このガイドでは、Active Recordのバリデーション (検証: validation) 機能を使って、オブジェクトがデータベースに保存される前にオブジェクトの状態を検証する方法について説明します。
このガイドの内容:
きわめてシンプルなバリデーションの例を以下に紹介します。
class Person < ApplicationRecord validates :name, presence: true end Person.create(name: "John Doe").valid? # => true Person.create(name: nil).valid? # => false
上からわかるように、このバリデーションはPersonにname属性がない場合に無効であることを知らせます。2つ目のPersonはデータベースに保存されません。
バリデーションの詳細を説明する前に、アプリケーション全体においてバリデーションがいかに重要であるかについて説明します。
バリデーションは、正しいデータだけをデータベースに保存するために行われます。たとえば、自分のアプリケーションで、すべてのユーザーには必ず電子メールアドレスとメーリングリストアドレスが必要だとします。正しいデータだけをデータベースに保存するのであれば、モデルレベルでバリデーションを実行するのが最適です。モデルレベルでのバリデーションは、データベースに依存せず、エンドユーザーがバイパスすることもできず、テストもメンテナンスもやりやすいためです。Railsではバリデーションを簡単に利用できるよう、一般に利用可能なビルトインヘルパーが用意されており、独自のバリデーションメソッドも作成できるようになっています。
データをデータベースに保存する前にバリデーションを実行する方法は、他にもデータベースネイティブの制約機能、クライアント側でのバリデーション、コントローラレベルのバリデーションなど、さまざまです。それぞれのメリットとデメリットは以下のとおりです。
上で紹介したその他のバリデーションについては、特定の状況に応じて適宜追加してください。Railsチームは、ほとんどの場合モデルレベルのバリデーションが最も適切であると考えています。
Active Recordのオブジェクトには2つの種類があります。オブジェクトがデータベースの行(row)に対応しているものと、そうでないものです。たとえば、newメソッドで新しくオブジェクトを作成しただけでは、オブジェクトはデータベースに属していません。saveメソッドを呼ぶことで、オブジェクトは適切なデータベースのテーブルに保存されます。Active Recordのnew_record?インスタンスメソッドを使うと、オブジェクトが既にデータベース上にあるかどうかを確認できます。
次の単純なActive Recordクラスを例に取ってみましょう。
class Person < ApplicationRecord end
rails consoleの出力で様子を観察してみます。
$ bin/rails console >> p = Person.new(name: "John Doe") => #<Person id: nil, name: "John Doe", created_at: nil, updated_at: nil> >> p.new_record? => true >> p.save => true >> p.new_record? => false
新規レコードを作成して保存すると、SQLのINSERT操作がデータベースに送信されます。既存のレコードを更新すると、SQLのUPDATE操作が送信されます。バリデーションは、SQLのデータベースへの送信前に行うのが普通です。バリデーションのいずれかが失敗すると、オブジェクトは無効(invalid)とマークされ、Active RecordでのINSERTやUPDATE操作は行われません。これにより、無効なオブジェクトがデータベースに保存されることを防止します。オブジェクトの作成、保存、更新時に特定のバリデーションを実行することもできます。
データベース上のオブジェクトの状態を変える方法が多数あることにご注意ください。 メソッドには、バリデーションをトリガするものと、しないものがあります。この点に注意しておかないと、バリデーションが設定されているにもかかわらず、データベース上のオブジェクトが無効な状態になってしまう可能性があります。
以下のメソッドではバリデーションがトリガされ、オブジェクトが有効な場合にのみデータベースに保存されます。
createcreate!savesave!updateupdate!!が末尾に付く破壊的メソッド(save!など)では、レコードが無効な場合に例外が発生します。
非破壊的なメソッドは、無効な場合に例外を発生しません。saveとupdateは無効な場合にfalseを返し、createは無効な場合に単にそのオブジェクトを返します。
以下のメソッドはバリデーションを行わずにスキップします。オブジェクトの保存は、有効無効にかかわらず行われます。これらのメソッドの利用には注意が必要です。
decrement!decrement_counterincrement!increment_countertoggle!touchupdate_allupdate_attributeupdate_columnupdate_columnsupdate_counters実は、saveにvalidate: falseを引数として与えると、saveのバリデーションをスキップできてしまいます。この手法は十分注意して使う必要があります。
save(validate: false)valid?とinvalid?Railsは、Active Recordオブジェクトを保存する直前にバリデーションを実行します。バリデーションで何らかのエラーが発生すると、オブジェクトを保存しません。
valid?メソッドを使って、バリデーションを手動でトリガすることもできます。valid?を実行するとバリデーションがトリガされ、オブジェクトにエラーがない場合はtrueが返され、そうでなければfalseが返されます。
これは以下のように実装できます。
class Person < ApplicationRecord validates :name, presence: true end Person.create(name: "John Doe").valid? # => true Person.create(name: nil).valid? # => false
Active Recordでバリデーションが行われた後は、errors.messagesインスタンスメソッドを使うと、発生したエラーにアクセスできます。このメソッドはエラーのコレクションを返します。
定義上は、バリデーション実行後にコレクションが空になった場合は有効です。
ただし、newでインスタンス化されたオブジェクトは、それが厳密には無効であってもエラーは出力されないので、注意が必要です。これは、createやsaveメソッドなどによってオブジェクトが保存されるときのみ、バリデーションが自動的に実行されるためです。
class Person < ApplicationRecord validates :name, presence: true end >> p = Person.new # => #<Person id: nil, name: nil> >> p.errors.messages # => {} >> p.valid? # => false >> p.errors.messages # => {name:["空欄にはできません"]} >> p = Person.create # => #<Person id: nil, name: nil> >> p.errors.messages # => {name:["空欄にはできません"]} >> p.save # => false >> p.save! # => ActiveRecord::RecordInvalid: Validation failed: 空欄にはできません >> Person.create! # => ActiveRecord::RecordInvalid: Validation failed: 空欄にはできません
invalid?は単なるvalid?と逆のチェックです。このメソッドはバリデーションをトリガし、オブジェクトでエラーが発生した場合はtrueを、そうでなければfalseを返します。
errors[]errors[:attribute]を使うと、特定のオブジェクトの属性が有効かどうかを確認できます。このメソッドは、:attributeのすべてのエラーの配列を返します。指定された属性でエラーが発生しなかった場合は、空の配列が返されます。
このメソッドが便利なのは、バリデーションを実行した後だけです。このメソッドはエラーのコレクションを調べるだけで、バリデーションそのものをトリガしないからです。このメソッドは、前述のActiveRecord::Base#invalid?メソッドとは異なります。このメソッドはオブジェクト全体の正当性については確認しないためです。オブジェクトの個別の属性についてエラーがあるかどうかだけを調べます。
class Person < ApplicationRecord validates :name, presence: true end >> Person.new.errors[:name].any? # => false >> Person.create.errors[:name].any? # => true
より高レベルなバリデーションエラーについては、バリデーションエラーの取り扱いセクションを参照してください。
errors.details 無効(invalid)な属性において、どのバリデーションが失敗したのか調べるのにerrors.details[:attribute]が利用できます。これは:errorがキーで、失敗したバリデーターのシンボルが値となるハッシュの配列を返します。
class Person < ApplicationRecord validates :name, presence: true end >> person = Person.new >> person.valid? >> person.errors.details[:name] # => [{error: :blank}]
カスタムバリデータでdetailsを使う方法については、バリデーションエラーの取り扱いセクションを参照してください。
Active Recordには、クラス定義の内側で直接使える定義済みのバリデーションヘルパーが多数用意されています。これらのヘルパーは、共通のバリデーションルールを提供します。バリデーションが失敗するたびに、オブジェクトのerrorsコレクションにエラーメッセージが追加され、そのメッセージは、バリデーションが行われる属性に関連付けられます。
どのヘルパーも任意の数の属性を受け付けることができるので、1行のコードを書くだけで多くの属性に対して同じバリデーションを実行できます。
:onオプションと:messageオプションはどのヘルパーでも使えます。:onオプションはバリデーションを実行するタイミングを指定し、:messageオプションバリデーション失敗時にerrorsコレクションに追加するメッセージを指定します。:onオプションは:createまたは:updateのいずれかの値を取ります。バリデーションヘルパーには、それぞれデフォルトのエラーメッセージが用意されています。:messageオプションが使われていない場合はデフォルトのメッセージが使われます。利用可能なヘルパーを1つずつ見ていきましょう。
acceptanceこのメソッドは、フォームが送信されたときにユーザーインターフェイス上のチェックボックスがオンになっているかどうかを検証します。ユーザーによるサービス利用条項への同意が必要な場合や、ユーザーが何らかの文書に目を通したことを確認させる場合によく使われます。
class Person < ApplicationRecord validates :terms_of_service, acceptance: true end
このチェックは、terms_of_serviceがnilでない場合にのみ実行されます。
このヘルパーのデフォルトエラーメッセージは「must be accepted」です。
次のようにカスタムメッセージをmessageオプションで渡すこともできます。
class Person < ApplicationRecord validates :terms_of_service, acceptance: { message: 'must be abided' } end
このヘルパーでは:acceptオプションも渡せます。このオプションは、「同意済み(accepted)」とみなす値を指定します。デフォルトは['1', true]ですが、変更は簡単です。
class Person < ApplicationRecord validates :terms_of_service, acceptance: { accept: 'yes' } validates :eula, acceptance: { accept: ['TRUE', 'accepted'] } end
これはWebアプリケーション特有のバリデーションであり、データベースに保存する必要はありません。これに対応するフィールドがなくても、単にヘルパーが仮想の属性を作成してくれます。このフィールドがデータベースに存在すると、acceptオプションを設定するかtrueを指定しなければならず、さもないとバリデーションが実行されません。
validates_associatedこのヘルパーは、モデルが他のモデルに関連付けられていて、両方のモデルに対してバリデーションを実行する必要がある場合に使うべきです。オブジェクトを保存しようとすると、関連付けられているオブジェクトごとにvalid?が呼び出されます。
class Library < ApplicationRecord has_many :books validates_associated :books end
このバリデーションは、あらゆる種類の関連付けで利用できます。
validates_associatedを関連付けの両側のオブジェクトで実行しないでください。
関連付けの両側でこのヘルパーを使うと無限ループになります。
validates_associatedのデフォルトエラーメッセージは「is invalid」です。関連付けられたオブジェクトにも自分のerrorsコレクションが含まれるので、エラーは呼び出し元のモデルに伝わりません。
confirmationこのヘルパーは、2つのテキストフィールドで受け取る内容が完全に一致する必要がある場合に使います。たとえば、メールアドレスやパスワードで、確認フィールドを使うとします。このバリデーションヘルパーは仮想の属性を作成します。属性の名前は、確認したい属性名に「_confirmation」を追加したものになります。
class Person < ApplicationRecord validates :email, confirmation: true end
ビューテンプレートで以下のようなフィールドを用意します。
<%= text_field :person, :email %> <%= text_field :person, :email_confirmation %>
このチェックは、email_confirmationがnilでない場合のみ行われます。確認を必須にするには、確認用の属性について存在チェックも追加してください。presenceを利用する存在チェックについてはこの後解説します。
class Person < ApplicationRecord validates :email, confirmation: true validates :email_confirmation, presence: true end
:case_sensitiveオプションを用いて、大文字小文字の違いを確認する制約をかけるかどうかも定義できます。デフォルトでは、このオプションはtrueになります。
class Person < ApplicationRecord validates :email, confirmation: { case_sensitive: false } end
このヘルパーのデフォルトメッセージは「doesn't match confirmation」です。
exclusionこのヘルパーは、与えられた集合に属性の値が「含まれていない」ことを検証します。集合には任意のenumerableオブジェクトが使えます。
class Account < ApplicationRecord validates :subdomain, exclusion: { in: %w(www us ca jp), message: "%{value}は予約済みです" } end
exclusionヘルパーの:inオプションには、バリデーションを行った属性の値に含めたくない値の集合を指定します。:inオプションには:withinというエイリアスもあり、好みに応じてどちらでも使えます。上の例では、:messageオプションを使って属性の値を含める方法を示しています。message引数の完全なオプションについては、:messageのドキュメントを参照してください。
デフォルトのエラーメッセージは「is reserved」です。
formatこのヘルパーは、withオプションで与えられた正規表現と属性の値がマッチするかどうかのテストによる検証を行います。
class Product < ApplicationRecord validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/, message: "英文字のみが使えます" } end
:withoutオプションを使うと、指定の属性にマッチしない正規表現を指定することもできます。
デフォルトのエラーメッセージは「is invalid」です。
inclusionこのヘルパーは、与えられた集合に属性の値が含まれているかどうかを検証します。集合には任意のenumerableオブジェクトが使えます。
class Coffee < ApplicationRecord validates :size, inclusion: { in: %w(small medium large), message: "%{value} のサイズは無効です" } end
inclusionヘルパーには:inオプションがあり、受け付ける値の集合を指定します。:inオプションには:withinというエイリアスもあり、好みに応じてどちらでも使えます。上の例では、属性の値をインクルードする方法を示すために:messageオプションも使っています。完全なオプションについては、:messageのドキュメントを参照してください。
このヘルパーのデフォルトのエラーメッセージは「is not included in the list」です。
lengthこのヘルパーは、属性の値の長さを検証します。多くのオプションがあり、さまざまな長さ制限を指定できます。
class Person < ApplicationRecord validates :name, length: { minimum: 2 } validates :bio, length: { maximum: 500 } validates :password, length: { in: 6..20 } validates :registration_number, length: { is: 6 } end
使用可能な長さ制限オプションは以下のとおりです。
:minimum: 属性はこの値より小さな値を取れません。:maximum: 属性はこの値より大きな値を取れません。:inまたは:within: 属性の長さは、与えられた区間以内でなければなりません。このオプションの値は範囲でなければなりません。:is: 属性の長さは与えられた値と等しくなければなりません。デフォルトのエラーメッセージは、実行されるバリデーションの種類によって異なります。デフォルトのメッセージは:wrong_length、:too_long、:too_shortオプションを使ってカスタマイズすることも、%{count}を長さ制限に対応する数値のプレースホルダにも使えます。:messageオプションを使ってエラーメッセージを指定することもできます。
class Person < ApplicationRecord validates :bio, length: { maximum: 1000, too_long: "最大%{count}文字まで使えます" } end
デフォルトのエラーメッセージは複数形で表現されていることにご注意ください (例: "is too short (minimum is %{count} characters)")。このため、:minimumを1に設定するのであればメッセージをカスタマイズして単数形にするか、代わりにpresence: trueを使います。:inまたは:withinの下限に1を指定する場合、メッセージをカスタマイズして単数形にするか、lengthより先にpresenceを呼ぶようにします。
numericalityこのヘルパーは、属性に数値のみが使われていることを検証します。デフォルトでは、整数または浮動小数点にマッチします。これらの冒頭に符号がある場合もマッチします。整数のみにマッチさせたい場合は、:only_integerをtrueにします。
:only_integerをtrueに設定すると、属性の値に対するバリデーションで以下の正規表現が使われます。
/\A[+-]?\d+\z/
それ以外の場合は、値をFloatで数値に変換しようとします。
class Player < ApplicationRecord validates :points, numericality: true validates :games_played, numericality: { only_integer: true } end
このヘルパーは、:only_integer以外にも以下のオプションで制約を指定できます。
:greater_than: 指定された値よりも大きくなければならないことを指定します。デフォルトのエラーメッセージは「must be greater than %{count}」です。:greater_than_or_equal_to: 指定された値と等しいか、それよりも大きくなければならないことを指定します。デフォルトのエラーメッセージは「must be greater than or equal to %{count}」です。:equal_to: 指定された値と等しくなければならないことを示します。デフォルトのエラーメッセージは「must be equal to %{count}」です。:less_than: 指定された値よりも小さくなければならないことを指定します。デフォルトのエラーメッセージは「must be less than %{count}」です。:less_than_or_equal_to: 指定された値と等しいか、それよりも小さくなければならないことを指定します。デフォルトのエラーメッセージは「must be less than or equal to %{count}」です。:other_than: 渡した値以外の値でなければならないことを指定します。デフォルトのエラーメッセージは「must be other than %{count}」です。:odd: trueの場合は奇数でなければなりません。デフォルトのエラーメッセージは「must be odd」です。:even: trueの場合は偶数でなければなりません。デフォルトのエラーメッセージは「must be even」です。デフォルトでは、numericalityのnil値は許容されません。allow_nil: trueオプションでnil値を許可できます。
デフォルトのエラーメッセージは「is not a number」です。
presenceこのヘルパーは、指定された属性が「空でない」ことを確認します。値がnilや空文字でない(つまり空欄でもなければホワイトスペースでもない)ことを確認するために、内部でblank?メソッドを使っています。
class Person < ApplicationRecord validates :name, :login, :email, presence: true end
関連付けが存在することを確認したい場合は、関連付けられたオブジェクト自体が存在することを確認し、そのオブジェクトが関連付けにマッピングされた外部キーでないことを確認する必要があります。
class LineItem < ApplicationRecord belongs_to :order validates :order, presence: true end
関連付けられたレコードの存在が必須の場合、これを検証するには:inverse_ofオプションでその関連付けを指定する必要があります。
class Order < ApplicationRecord has_many :line_items, inverse_of: :order end
このヘルパーを使って、has_oneまたはhas_manyリレーションシップを経由して関連付けられたオブジェクトが存在することを検証すると、blank?でもなくmarked_for_destruction?(削除用マーク済み)でもないかどうかがチェックされます。
false.blank?は常にtrueなので、真偽値に対してこのメソッドを使うと正しい結果が得られません。真偽値の存在をチェックしたい場合は、validates :field_name, inclusion: { in: [true, false] }を使う必要があります。
validates :boolean_field_name, inclusion: { in: [true, false] } validates :boolean_field_name, exclusion: { in: [nil] }
これらのバリデーションのいずれかを使うことで、値が決してnilにならないようにできます。nilがあると、ほとんどの場合NULL値になります。
absenceこのヘルパーは、指定された属性が「空」であることを検証します。値がnilや空文字である (つまり空欄またはホワイトスペースである) かどうかを確認するために、内部ではpresent?メソッドを使っています。
class Person < ApplicationRecord validates :name, :login, :email, absence: true end
関連付けが存在しないことを確認したい場合は、関連付けられたオブジェクト自体が存在しないかどうかを確認し、そのオブジェクトが関連付けにマッピングされた外部キーでないことを確認する必要があります。
class LineItem < ApplicationRecord belongs_to :order validates :order, absence: true end
関連付けられたレコードが存在してはならない場合、これを検証するには:inverse_ofオプションでその関連付けを指定する必要があります。
class Order < ApplicationRecord has_many :line_items, inverse_of: :order end
このヘルパーを使って、has_oneまたはhas_manyリレーションシップを経由して関連付けられたオブジェクトが存在しないことを検証すると、presence?でもなくmarked_for_destruction?(削除するためにマークされている)でもないかどうかがチェックされます。
false.present?は常にfalseなので、真偽値に対してこのメソッドを使うと正しい結果が得られません。真偽値が存在しないことをチェックしたい場合は、validates :field_name, exclusion: { in: [true, false] }を使う必要があります。
デフォルトのエラーメッセージは「must be blank」です。
uniquenessこのヘルパーは、オブジェクトが保存される直前に、属性の値が一意(unique)であり重複していないことを検証します。このヘルパーは一意性の制約をデータベース自体には作成しないので、本来一意にすべきカラムに、たまたま2つのデータベース接続によって同じ値を持つレコードが2つ作成される可能性が残ります。これを避けるには、データベースの両方のカラムに一意インデックスを作成する必要があります。
class Account < ApplicationRecord validates :email, uniqueness: true end
このバリデーションは、その属性と同じ値を持つ既存のレコードがモデルのテーブルにあるかどうかを調べるSQLクエリを実行することで行われます。
このヘルパーには、一意性チェックの範囲を限定する別の属性を指定する:scopeオプションがあります。
class Holiday < ApplicationRecord validates :name, uniqueness: { scope: :year, message: "発生は年に1度までである必要があります" } end
:scopeを用いる一意性バリデーションの違反を防止する目的でデータベース側に制約を作成したい場合は、データベース側で両方のカラムにuniqueインデックスを作成しなければなりません。MySQLのマニュアルでマルチカラムインデックスについての情報を参照するか、PostgreSQLのマニュアルでカラムのグループを参照する一意性制約についての例を参照してください。
このヘルパーには:case_sensitiveというオプションもあります。これは一意性制約で大文字小文字を区別するかどうかを指定します。このオプションはデフォルトでtrueです。
class Person < ApplicationRecord validates :name, uniqueness: { case_sensitive: false } end
一部のデータベースでは大文字小文字を区別しない設定になっていることがあります。
デフォルトのエラーメッセージは「has already been taken」です。
validates_withこのヘルパーは、バリデーション専用の別クラスにレコードを渡します。
class GoodnessValidator < ActiveModel::Validator def validate(record) if record.first_name == "Evil" record.errors[:base] << "これは悪人だ" end end end class Person < ApplicationRecord validates_with GoodnessValidator end
record.errors[:base]には、特定の属性よりもそのレコード全体の状態に関連するエラーメッセージを追加するのが普通です。
validates_withは、バリデーションに使う1つのクラス(またはクラスのリスト)を引数に取ります。validates_withにはデフォルトのエラーメッセージはありません。エラーメッセージが必要な場合は、バリデータクラスのレコードのエラーコレクションに手動で追加する必要があります。
バリデーションメソッドを実装するには、定義済みのrecordパラメータが必要です。このパラメータはバリデーションを行なうレコードを表します。
他のバリデーションと同様、validates_withヘルパーでも:if、:unless、:onオプションが使えます。その他のオプションは、バリデータクラスにoptionsとして渡されます。
class GoodnessValidator < ActiveModel::Validator def validate(record) if options[:fields].any?{|field| record.send(field) == "Evil" } record.errors[:base] << "これは悪人だ" end end end class Person < ApplicationRecord validates_with GoodnessValidator, fields: [:first_name, :last_name] end
このバリデータは、アプリケーションのライフサイクル内で一度しか初期化されない点にご注意ください。バリデーションが実行されるたびに初期化されることはありません。インスタンス変数の扱いには十分ご注意ください。
作成したバリデータが複雑になってインスタンス変数を使いたくなった場合は、代わりに素のRubyオブジェクトを使うこともできます。
class Person < ApplicationRecord validate do |person| GoodnessValidator.new(person).validate end end class GoodnessValidator def initialize(person) @person = person end def validate if some_complex_condition_involving_ivars_and_private_methods? @person.errors[:base] << "これは悪人だ" end end # ... end
validates_eachこのヘルパーは、1つのブロックに対して属性を検証します。定義済みのバリデーション関数はありません。このため、ブロックを使うバリデーションを自分で作成し、validates_eachに渡す属性がすべてブロックに対してテストされるようにする必要があります。以下の例では、苗字と名前が小文字で始まらないようにしたいと考えています。
class Person < ApplicationRecord validates_each :name, :surname do |record, attr, value| record.errors.add(attr, 'must start with upper case') if value =~ /\A[a-z]/ end end
このブロックは、レコードと属性の名前、そして属性の値を受け取ります。ブロック内でこれらを使ってデータが正しいかどうかを自由にチェックできます。バリデーションに失敗した場合にはモデルにエラーメッセージを追加し、バリデーションが無効になるようにしてください。
共通で使えるバリデーションオプションを以下に示します。
:allow_nil:allow_nilオプションは、対象の値がnilの場合にバリデーションをスキップします。
class Coffee < ApplicationRecord validates :size, inclusion: { in: %w(small medium large), message: "%{value}は有効な値ではありません" }, allow_nil: true end
message:引数の完全なオプションについては、:messageのドキュメントを参照してください。
:allow_blank:allow_blankオプションは:allow_nilオプションと似ています。このオプションを指定すると、属性の値がblank?に該当する場合(nilや空文字など)にバリデーションがパスします。
class Topic < ApplicationRecord validates :title, length: { is: 5 }, allow_blank: true end Topic.create(title: "").valid? # => true Topic.create(title: nil).valid? # => true
:message既に例示したように、:messageオプションを使うことで、バリデーション失敗時にerrorsコレクションに追加されるカスタムエラーメッセージを指定できます。このオプションを使わない場合、Active Recordはバリデーションヘルパーごとにデフォルトのエラーメッセージを使います。:messageオプションはStringかProcを受け取ります。
Stringの:message値には、%{value}や%{attribute}や%{model}をオプションで含められます。これらはバリデーション失敗時に動的に置き換えられます。置き換えにはI18n gemが用いられており、プレースホルダーはこのとおりでなければならず、スペース文字を含めることはできません。
Procの:message値には引数が2つ与えられます。バリデーションの対象となるオブジェクトと、:modelと:attributeと:valueのキー/値ペアを含むハッシュです。
class Person < ApplicationRecord # メッセージを直書きした場合 validates :name, presence: { message: "must be given please" } # 動的な属性値を含むメッセージの場合。%{value}は実際の属性値に # 置き換えられる。%{attribute}や%{model}も利用可能。 validates :age, numericality: { message: "%{value} seems wrong" } # Procの場合 validates :username, uniqueness: { # object = バリデーションされる人物のオブジェクト # data = { model: "Person", attribute: "Username", value: <username> } message: ->(object, data) do "Hey #{object.name}!, #{data[:value]} is taken already! Try again #{Time.zone.tomorrow}" end } end
:on:onオプションは、バリデーション実行のタイミングを指定します。ビルトインのバリデーションヘルパーは、デフォルトでは保存時(レコードの作成時および更新時の両方)に実行されます。バリデーションのタイミングを変更したい場合、on: :createを指定すればレコード新規作成時にのみ検証が行われ、on: :updateを指定すればレコードの更新時にのみ検証が行われます。
class Person < ApplicationRecord # 値が重複していてもemailを更新できる validates :email, uniqueness: true, on: :create # 新規レコード作成時に、数字でない年齢表現を使える validates :age, numericality: true, on: :update # デフォルト (作成時と更新時の両方でバリデーションを行なう) validates :name, presence: true end
on:ではカスタムコンテキストも定義できます。カスタムコンテキストは、valid?やinvalid?やsaveにcontext: コンテキスト名を渡して明示的にトリガーする必要があります。
class Person < ApplicationRecord validates :email, uniqueness: true, on: :account_setup validates :age, numericality: true, on: :account_setup end person = Person.new(age: 'thirty-three') person.valid? # => true person.valid?(:account_setup) # => false person.errors.messages # => {:email=>["has already been taken"], :age=>["is not a number"]}
person.valid?(:account_setup)は、モデルを保存せずにバリデーションを2つとも実行します。person.save(context: :account_setup)は、account_setupコンテキストでpersonをバリデーションしてから保存します。
明示的なトリガーによるモデルのバリデーションでは、そのコンテキストのみのバリデーションと、コンテキストなしのバリデーションが行われます。
class Person < ApplicationRecord validates :email, uniqueness: true, on: :account_setup validates :age, numericality: true, on: :account_setup validates :name, presence: true end person = Person.new person.valid?(:account_setup) # => false person.errors.messages # => {:email=>["has already been taken"], :age=>["is not a number"], :name=>["can't be blank"]}
バリデーションを厳密にし、オブジェクトが無効だった場合にActiveModel::StrictValidationFailedが発生するようにすることもできます。
class Person < ApplicationRecord validates :name, presence: { strict: true } end Person.new.valid? # => ActiveModel::StrictValidationFailed: Name can't be blank
カスタム例外を:strictオプションに追加することもできます。
class Person < ApplicationRecord validates :token, presence: true, uniqueness: true, strict: TokenGenerationException end Person.new.valid? # => TokenGenerationException: Token can't be blank
特定の条件を満たす場合にのみバリデーションを実行したい場合があります。:ifオプションや:unlessオプションを使うことでこのような条件を指定できます。引数にはシンボル、ProcまたはArrayを使えます。:ifオプションは、特定の条件でバリデーションを行なうべきである場合に使います。特定の条件でバリデーションを行なうべきでない場合は、:unlessオプションを使います。
:ifや:unlessでシンボルを使うバリデーションの実行直前に呼び出されるメソッド名をシンボルで:ifや:unlessオプションに指定することもできます。
これは最も頻繁に使われるオプションです。
class Order < ApplicationRecord validates :card_number, presence: true, if: :paid_with_card? def paid_with_card? payment_type == "card" end end
:ifや:unlessでProcを使う呼び出したいProcオブジェクトを:ifや:unlessで使うこともできます。Procオブジェクトを使うと、個別のメソッドを指定する代わりに、その場で条件を書けるようになります。ワンライナーに収まる条件を使いたい場合に最適です。
class Account < ApplicationRecord validates :password, confirmation: true, unless: Proc.new { |a| a.password.blank? } end
LambdasはProcの一種なので、これを用いて以下のようにインライン条件をもっと短く書くこともできます。
validates :password, confirmation: true, unless: -> { password.blank? }
1つの条件を複数のバリデーションで共用できると便利なことがあります。これはwith_optionsを使うことで簡単に実現できます。
class User < ApplicationRecord with_options if: :is_admin? do |admin| admin.validates :password, length: { minimum: 10 } admin.validates :email, presence: true end end
with_optionsブロックの内側にあるすべてのバリデーションには、if: :is_admin?という条件が渡されます。
逆に、バリデーションを行なう条件を複数定義したい場合、Arrayを使えます。さらに、1つのバリデーションに:ifと:unlessを両方使うこともできます。
class Computer < ApplicationRecord validates :mouse, presence: true, if: [Proc.new { |c| c.market.retail? }, :desktop?], unless: Proc.new { |c| c.trackpad.present? } end
このバリデーションは、:if条件がすべてtrueで、かつ:unlessが1つもtrueにならない場合にのみ実行されます。
ビルトインのバリデーションヘルパーだけでは不足の場合、好みのバリデータやバリデーションメソッドを作成して使えます。
カスタムバリデータ (validator) は、ActiveModel::Validatorを継承するクラスです。これらのクラスでは、validateメソッドを実装する必要があります。このメソッドはレコードを1つ引数に取り、それに対してバリデーションを実行します。カスタムバリデータはvalidates_withメソッドを使って呼び出します。
class MyValidator < ActiveModel::Validator def validate(record) unless record.name.starts_with? 'X' record.errors[:name] << '名前はXで始まる必要があります' end end end class Person include ActiveModel::Validations validates_with MyValidator end
個別の属性を検証するためのカスタムバリデータを追加するには、ActiveModel::EachValidatorを使用するのが最も簡単で便利です。この場合、このカスタムバリデータクラスはvalidate_eachメソッドを実装する必要があります。このメソッドは、そのインスタンスに対応するレコード、バリデーションを行う属性、そして渡されたインスタンスの属性の値の3つの引数を取ります。
class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i record.errors[attribute] << (options[:message] || "はメールアドレスではありません") end end end class Person < ApplicationRecord validates :email, presence: true, email: true end
上の例に示したように、標準のバリデーションとカスタムバリデーションを組み合わせることもできます。
モデルの状態を確認し、無効な場合にerrorsコレクションにメッセージを追加するメソッドを作成できます。これらのメソッドを作成後、validate(API)クラスメソッドを使って登録し、バリデーションメソッド名を指すシンボルを渡す必要があります。
1つのクラスメソッドには複数のシンボルを渡せます。バリデーションは登録されたとおりの順序で実行されます。
valid?メソッドはerrorsコレクションが空であることを検証するので、カスタムバリデーションはバリデーションが失敗したときにエラーを追加すべきです。
class Invoice < ApplicationRecord validate :expiration_date_cannot_be_in_the_past, :discount_cannot_be_greater_than_total_value def expiration_date_cannot_be_in_the_past if expiration_date.present? && expiration_date < Date.today errors.add(:expiration_date, ": 過去の日付は使えません") end end def discount_cannot_be_greater_than_total_value if discount > total_value errors.add(:discount, "合計額を上回ることはできません") end end end
これらのバリデーションは、デフォルトではvalid?を呼び出すかオブジェクトを保存するたびに実行されます。しかし:onオプションを使えば、カスタムバリデーションが実行されるタイミングを変更できます。validateに対してon: :createまたはon: :updateを指定します。
class Invoice < ApplicationRecord validate :active_customer, on: :create def active_customer errors.add(:customer_id, "is not active") unless customer.active? end end
既に説明したvalid?メソッドやinvalid?メソッドの他に、Railsではerrorsコレクションに対応し、オブジェクトの正当性を検査するメソッドが多数用意されています。
以下は最もよく使われるメソッドの一覧です。利用可能なすべてのメソッドについては、ActiveModel::Errorsドキュメントを参照してください。
errorsすべてのエラーを含むActiveModel::Errorsクラスのインスタンスを1つ返します。キーは属性名、値はすべてのエラー文字列の配列です。
class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end person = Person.new person.valid? # => false person.errors.messages # => {:name=>["空欄にはできません", "短すぎます (最小3文字)"]} person = Person.new(name: "John Doe") person.valid? # => true person.errors.messages # => {}
errors[]errors[]は、特定の属性についてエラーメッセージをチェックしたい場合に使います。指定の属性に関するすべてのエラーメッセージの文字列の配列を返します。1つの文字列が1つのエラーメッセージに対応します。属性に関連するエラーがない場合は空の配列を返します。
class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end person = Person.new(name: "John Doe") person.valid? # => true person.errors[:name] # => [] person = Person.new(name: "JD") person.valid? # => false person.errors[:name] # => ["が短すぎます (最小3文字)"] person = Person.new person.valid? # => false person.errors[:name] # => ["空欄にはできません", "短すぎます (最小3文字)"]
errors.addaddメソッドを使って、特定の属性に関連するエラーメッセージを手動で追加できます。このメソッドは属性とエラーメッセージを引数として受け取ります。
errors.full_messages(または同等のerrors.to_a)メソッドは、エラーメッセージをユーザーが読みやすい形式で返します。以下のように、メッセージごとに頭が大文字の属性名を冒頭に追加します。
class Person < ApplicationRecord def a_method_used_for_validation_purposes errors.add(:name, "は以下の文字を含むことができません !@#%*()_-+=") end end person = Person.create(name: "!@#") person.errors[:name] # => ["は以下の文字を含むことができません !@#%*()_-+="] person.errors.full_messages # => ["Name は以下の文字を含むことができません !@#%*()_-+="]
errors.detailserrors.addメソッドを使って、返されたエラーのdetailsハッシュにバリデータの種類を指定できます。
class Person < ApplicationRecord def a_method_used_for_validation_purposes errors.add(:name, :invalid_characters) end end person = Person.create(name: "!@#") person.errors.details[:name] # => [{error: :invalid_characters}]
たとえば、使ってはならない文字セットをエラーのdetailsに追加するには、errors.addで追加のキーを渡します。
class Person < ApplicationRecord def a_method_used_for_validation_purposes errors.add(:name, :invalid_characters, not_allowed: "!@#%*()_-+=") end end person = Person.create(name: "!@#") person.errors.details[:name] # => [{error: :invalid_characters, not_allowed: "!@#%*()_-+="}]
Railsに組み込まれているどのバリデータも、対応するバリデータの種類を持つdetailsハッシュを展開できます。
errors[:base]個別の属性に関連するエラーメッセージを追加する代わりに、オブジェクトの状態全体に関連するエラーメッセージを追加することもできます。このメソッドは、属性がどんな値であってもオブジェクトが無効であることを通知したい場合に使えます。errors[:base]は配列なので、これに文字列を単に追加するだけでエラーメッセージとして使えるようになります。
class Person < ApplicationRecord def a_method_used_for_validation_purposes errors[:base] << "この人物は以下の理由で無効です..." end end
errors.clearclearメソッドは、errorsコレクションに含まれるメッセージをすべてクリアしたい場合に使えます。無効なオブジェクトに対してerrors.clearメソッドを呼び出しても、それだけでオブジェクトが有効になるわけではありませんのでご注意ください。errorsは空になりますが、valid?やオブジェクトをデータベースに保存しようとするメソッドが次回呼び出されたときに、バリデーションが再実行されます。そしていずれかのバリデーションが失敗すると、errorsコレクションに再びメッセージが格納されます。
class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end person = Person.new person.valid? # => false person.errors[:name] # => ["空欄にはできません", "短すぎます (最小3文字)"] person.errors.clear person.errors.empty? # => true person.save # => false person.errors[:name] # => ["空欄にはできません", "短すぎます (最小3文字)"]
errors.sizesizeメソッドは、そのオブジェクトのエラーメッセージの総数を返します。
class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end person = Person.new person.valid? # => false person.errors.size # => 2 person = Person.new(name: "Andrea", email: "andrea@example.com") person.valid? # => true person.errors.size # => 0
モデルを作成してバリデーションを追加し、Webのフォーム経由でそのモデルが作成できるようになったら、そのモデルでバリデーションが失敗したときにエラーメッセージを表示したくなります。
エラーメッセージの表示方法はアプリケーションごとに異なるため、そうしたメッセージを直接生成するビューヘルパーはRailsに含まれていません。
しかし、Railsでは一般的なバリデーションメソッドが多数提供されているので、カスタムのメソッドを作成するのは比較的簡単です。また、生成をscaffoldで行なうと、そのモデルのエラーメッセージをすべて表示するERBがRailsによって一部の_form.html.erbファイルに追加されます。
@postという名前のインスタンス変数に保存されたモデルがあるとすると、以下のようになります。
<% if @post.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@post.errors.count, "error") %> はこの投稿の保存を禁止しています:</h2> <ul> <% @post.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>
また、フォームをRailsのフォームヘルパーで生成した場合、あるフィールドでバリデーションエラーが発生すると、そのエントリの周りに追加の<div>が自動的に生成されます。
<div class="field_with_errors"> <input id="post_title" name="post[title]" size="30" type="text" value=""> </div>
この<div>タグに好みのスタイルを追加できます。Railsが生成するデフォルトのscaffoldによって、以下のCSSルールが追加されます。
.field_with_errors {
padding: 2px;
background-color: red;
display: table;
}
このCSSは、エラーを含むフィールドを2ピクセルの赤い枠で囲みます。
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。