バートリーのさいとうです。
今回は
【Rails】バリデーションスキップができない時に考えるべき、4つのバリデーションレベルそれぞれの役割
というテーマでお話ししていきます。
Ruby on Railsでは、バリデーションの仕組みが充実しており、かなり柔軟にバリデーションをかけることができます。
詳細については、Railsガイドのこちらの章をご覧になってください。
今回の記事では、このバリデーションの仕組みを利用している際に
「特定の条件下」ではActiveRecordモデルに対して付与しているバリデーションをスキップしたい
と考えているが、なぜかできない…という方に向けて、バリデーションレベルについて解説する形で説明していきます。
バリデーションをスキップする方法についてはRailsガイドのバリデーションスキップについての章をご覧ください。
これは実際に僕がどん詰まりしたところでして、その理由と必要な知識だったバリデーションレベルについて理解するために備忘録として、この記事を書きます。
この記事を読めば
- railsにおけるバリデーションには、4つのレベルが存在することがわかる
- db層とrails層のバリデーションの違いを理解できる
- railsのバリデーションをスキップできない時に、考えるべき方向性がわかる
ので、思っているようにバリデーションスキップができていないぞ…と思った方、「そうやって考えればいいのね。」ということを理解するつもりで読んでもらえればと思います。
バリデーションの4つのレベル
では早速、バリデーションの4つのレベルをご紹介します。
(レベルの切り分けについては、こちらの記事を参考にしています。)
- フロントエンドレベル
- コントローラレベル
- モデルレベル
- DBレベル
この4つのレベルを、データの流れで考えると、このようにまとめることができます。

この画像でいくと、右に行くほどバリデーションの優先度が増していきます。
つまり、DBレベルのバリデーションが最も優先されるということです。
この理由は、バリデーションの本来の目的である
バリデーションは、正しいデータだけをデータベースに保存するために行われます。
Rails ガイド Active Record バリデーション 1.1 バリデーションを行なう理由
を達成するためです。
DBに入る前にアプリケーションの層がデータをブロックしていたとしても、肝心のDBにガードマンがいなければ、誤って通してしまったデータは保存されます。
このため、最後の砦として、DBレベルのバリデーションは付与されるべきであり、このバリデーションが最も強固ということになります。
では、個別のバリデーションレベルの特徴を見ていきましょう。
フロントエンドレベル
例えば、JSでフォームの空欄チェックを行う例を考えてください。
JSで入力しているかどうかをチェックしているとはいえ、もしなんらかの方法で機能しなくなった場合、悠々と入力された情報がDBに保存されていきます。
一方で、フロントエンドバリデーションを使うメリットとしては、適切ではないデータのまま送信されてしまう場合に事前にその処理をストップできることですね。
ただ、=(イコール)DBに保存されないというわけではないので、気を付けましょう。
コントローラーレベル
コントローラーレベルのバリデーションは、例えば、ここからここまでという期間が決められているアクションを期間外に呼び出そうとすると、リダイレクトして元の画面に戻る、などといったものです。
あとは、モデルに記載されているバリデーションを特定の時だけ呼び出すなど、モデルに記述するバリデーションが複雑にならないようにすることもできます。
しかし、このレベルのバリデーションもフロントエンドレベルよりは強固なものの、DBへの保存は直接ブロックしていません。
なので、後の二つでより強固なバリデーションを構築していきます。
モデルレベル
これが、最も使われるレベルです。以下のような記述で定義できます。
class Use < ActiveRecord::Base
validate :name, presence: true
end
ActiveRecord::Baseを継承したクラスに定義されているvalidateの内容を、保存や更新時に呼び出します。
しかしここで重要なのは、いくらモデルでpresence: ture(存在チェック)を付与しても、その壁を乗り越えられたらDBには保存されるということです。
僕は、まだまだひよっこエンジニアなので、ここの仕組みを理解するのに苦しみました。
具体的には、ActiveRecord::Baseを継承する代わりに、サービスクラスやformObjectのように、いわゆるFatModelを避けるためにビジネスロジックを切り出す際です。
このような切り出しを行った時には、バリデーションも切り出すことが多いでしょう。
ここで頭が混乱しました。
そこで、理解の手助けになるのは、Rails層とDB層に切り分けてバリデーションを考えることです。
ここまで挙げた三つのバリデーションレベルは、全てRails層に働くバリデーションでした。
しかし、先述の通り最も優先されるのはDBレベルバリデーションです。
そのため、もしrails層でガチガチにブロックを固めても、突破されてしまえば元も子もありません。
なので、次に見るDBレベルバリデーションがもしカラムに定義されていれば、それを最優先に守りながら設計する必要が出てきます。
DBレベル
DBレベルは、具体的にいうとマイグレーションファイルに記載される以下のような記述で定義されます。
class AddNicknameToUsers < ActiveRecord::Migration
def change
add_column :users, :nickname, :string, null: false
end
end
nicknameカラムをusersテーブルに追加するマイグレーションファイルを例に取ります。
オプションとして、null: falseが指定されています。つまり、必ず入力されている必要があります。
よって、このnicknameは、DBレベルでバリデーションが定義されることになり、Rails層で何があろうと必ず入力しなければならないカラムになりました。
バリデーションスキップできない理由
ここまで理解してもらって、ようやく本題です。
先ほどマイグレーションファイルに記述したnicknameカラムをマイグレーションした後に、Rails層側で以下のように記述して保存させてみます。
class Use < ActiveRecord::Base
validate :name, presence: true
validate :nickname, presence: true
end
#users_controller.rb
def create
user = User.new(name: "さいとう", nickname: "")
if user.save(validate: false)
redirect_to :index
else
render :new
end
end
validate: falseを付与されたsaveやcreateは、モデルに定義されているバリデーションをスキップして、保存や更新が可能になります。
しかし、nicknameカラムには先述の通りDBレベルでnull: falseがかかっているので、ターミナルを確認すると、利用しているDBから、null: falseがかかっているので、登録できませんよ!的なアラートを確認できるはずです。
これが、バリデーションスキップができているようでできていない理由です。
つまり、まとめるとこうなります。
- DBレベルバリデーションがかかっていないか確認する
- かかっている場合は、そのバリデーションを最優先に設計する
- ないのに失敗している場合はRails層の問題なので、記述を変更する
以上が、バリデーションレベルの解説と、バリデーションスキップができない理由の解説となります。
まとめ
今回は、【Rails】バリデーションスキップができない時に考えるべき、4つのバリデーションレベルそれぞれの役割というテーマで解説してきました。
実務の中では、例に出したものより更に複雑なバリデーションが入り混ざっており、本当に大変。
頑張ろう。
このブログは、月に15本を目標に、実務から学んだプログラミングのあれこれを発信しています。
誰かのためになれば、嬉しいです。
最後まで読んでいただき、ありがとうございました。
それでは。