このガイドでは、Active Recordのバリデーション(検証: validation)機能を使って、オブジェクトがデータベースに保存される前にオブジェクトの状態を検証する方法について説明します。
このガイドの内容:
きわめてシンプルなバリデーションの例を以下に紹介します。
class Person < ApplicationRecord validates :name, presence: true end
irb> Person.new(name: "John Doe").valid? #=> true irb> Person.new(name: nil).valid? #=> false
上でわかるように、このバリデーションはPersonにname属性が存在しない場合に無効であることを知らせます。2つ目のPersonはデータベースに保存されません。
バリデーションについて詳しく説明する前に、アプリケーション全体においてバリデーションがいかに重要であるかについて説明します。
バリデーションの目的は、有効なデータだけをデータベースに保存し、無効なデータがデータベースに紛れ込まないようにすることです。 たとえば、すべてのユーザーが有効なメールアドレスと郵送先住所を提供していることを保証することがアプリケーションにとって重要な場合があります。
正しいデータだけをデータベースに保存するのであれば、モデルレベルでバリデーションを実行するのが最適です。モデルレベルでのバリデーションは、データベースの種類やバージョンに依存せず、エンドユーザーがバイパスすることもできず、テストもメンテナンスもやりやすいためです。
Railsではバリデーションを簡単に利用できるよう、一般に利用可能なバリデーションヘルパーが組み込まれており、独自のバリデーションメソッドも作成できるようになっています。
データをデータベースに保存する前に検証を自動実行する方法は、他にも「データベースネイティブの制約機能」「クライアント(ブラウザ)側でのバリデーション」「コントローラレベルのバリデーション」など、さまざまな方法があります。
それぞれのメリットとデメリットは以下のとおりです。
データベースレベルの制約やストアドプロシージャを使うと、バリデーションのメカニズムがデータベースに依存してしまい、テストや保守がその分面倒になる可能性があります。 ただし、データベースが(Rails以外の)他のアプリケーションからも使われるのであれば、データベースレベルである程度のバリデーションを行なっておくのはよい方法です。 また、データベースレベルのバリデーションの中には、利用頻度がきわめて高いテーブルの一意性バリデーションのように、他の方法では実装が困難なものもあります。
クライアント(ブラウザ)側でのバリデーションは扱いやすく便利ですが、一般に単独では信頼性が不足します。 JavaScriptを使ってバリデーションを実装する場合、ユーザーがJavaScriptをオフにすればバイパスされてしまいます。 ただし、他の方法と併用するのであれば、クライアント側でのバリデーションはユーザーに入力ミスを即座に通知する便利な方法として利用できます。
コントローラレベルのバリデーションは一度はやってみたくなるものですが、たいてい手に負えなくなり、テストも保守も困難になりがちなので良くありません。 アプリケーションの寿命を延ばし、メンテナンス作業を苦痛にしないためにも、コントローラのコード量は可能な限り減らすべきです。
Railsチームは、ほとんどの場合モデルレベルのバリデーションが最も適切であると考えていますが、場合によっては上述の別の検証方法を併用することもあります。
Active Recordのオブジェクトには2つの種類があります。データベースの行(row)に対応しているオブジェクトと、そうでないオブジェクトです。
たとえば、newメソッドで新しくオブジェクトを作成しただけでは、オブジェクトはデータベースに属していません。saveメソッドを呼ぶことで、オブジェクトは適切なデータベースのテーブルに保存されます。
Active Recordのpersisted?インスタンスメソッド(またはその逆のnew_record?)を使うと、オブジェクトが既にデータベース上にあるかどうかを確認できます。
以下のActive Recordクラスを例として考えてみましょう。
class Person < ApplicationRecord end
bin/rails consoleの出力で様子を観察してみます。
irb> p = Person.new(name: "Jane Doe") #=> #<Person id: nil, name: "Jane Doe", created_at: nil, updated_at: nil> irb> p.new_record? #=> true irb> p.persisted? #=> false irb> p.save #=> true irb> p.new_record? #=> false irb> p.persisted? #=> true
新規レコードを保存すると、SQLのINSERT操作がデータベースに送信され、既存のレコードを更新すると、SQLのUPDATE操作が送信されます。バリデーションは、これらのコマンドがデータベースに送信される前に実行されるのが普通です。
バリデーションが失敗すると、オブジェクトは無効(invalid)とマーキングされ、Active RecordによるINSERTやUPDATE操作は実行されません。このようにして、無効なオブジェクトがデータベースに保存されるのを防ぎます。
オブジェクトの作成、保存、更新時に特定のバリデーションを実行することも可能です。
Railsのバリデーション機能は、無効なデータがデータベースに保存されるのを基本的に防ぎますが、Railsのメソッドの中にはバリデーションをトリガーしないものもある点に注意することが重要です。バリデーションをバイパスする一部のメソッドを使うと、バリデーションをトリガーせずにデータベースに直接変更を加えることが可能になるため、注意しておかないとオブジェクトを無効な状態で保存してしまう可能性があります。
以下のメソッドを実行するとバリデーションがトリガーされ、オブジェクトが有効な場合にのみデータベースに保存されます。
!が末尾に付く破壊的メソッド(save!など)では、レコードが無効な場合に例外が発生します。
逆に!なしの非破壊的なメソッドは、無効な場合に例外を発生しません。
saveとupdateは無効な場合にfalseを返し、createは無効な場合に単にそのオブジェクトを返します。
以下のメソッドはバリデーションを行わずにスキップします。オブジェクトの保存は、有効無効にかかわらず行われます。これらのメソッドの利用には注意が必要です。詳しくは個別のAPIドキュメントを参照してください。
decrement!decrement_counterincrement!increment_counterinsertinsert!insert_allinsert_all!toggle!touchtouch_allupdate_allupdate_attributeupdate_attribute!update_columnupdate_columnsupdate_countersupsertupsert_allsave(validate: false)実は、saveにvalidate: falseを引数として与えると、saveのバリデーションをスキップすることが可能です。この手法は十分注意して使う必要があります。
Railsは、Active Recordオブジェクトを保存する直前にバリデーションを実行します。バリデーションで何らかのエラーが発生すると、オブジェクトを保存しません。
valid?メソッドを使って、バリデーションを手動でトリガーすることもできます。valid?を実行するとバリデーションがトリガーされ、オブジェクトにエラーがない場合はtrueを返し、エラーの場合はfalseを返します。
これは以下のように実装できます。
class Person < ApplicationRecord validates :name, presence: true end
irb> Person.new(name: "John Doe").valid? #=> true irb> Person.new(name: nil).valid? #=> false
Active Recordでバリデーションが行われた後でerrorsインスタンスメソッドを使うと、失敗したバリデーションにアクセスできます。このメソッドはエラーのコレクションを返します。
定義により、バリデーション実行後にコレクションが空である場合は、オブジェクトが有効になる点にご注意ください。
ただし、newでインスタンス化した保存前のオブジェクトは、たとえ技術的には無効であってもエラーは出力されないので、注意が必要です。バリデーションが自動的に実行されるのは、createやsaveメソッドなどでオブジェクトが保存されたときだけです。
class Person < ApplicationRecord validates :name, presence: true end
irb> person = Person.new #=> #<Person id: nil, name: nil, created_at: nil, updated_at: nil> irb> person.errors.size #=> 0 irb> person.valid? #=> false irb> person.errors.objects.first.full_message #=> "Name can't be blank" irb> person.save #=> false irb> person.save! ActiveRecord::RecordInvalid: Validation failed: Name can't be blank irb> Person.create! ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
invalid?はvalid?と逆のチェックを行います。このメソッドはバリデーションをトリガーし、オブジェクトでエラーが発生した場合はtrueを返し、エラーがない場合はfalseを返します。
errors[:attribute]を使うと、特定のオブジェクトの属性が有効かどうかを確認できます。このメソッドは、:attributeのすべてのエラーの配列を返します。指定された属性でエラーが発生しなかった場合は、空の配列が返されます。これを用いて、特定の属性でバリデーションに問題があるかどうかを手軽に判断できます。
属性が正しいかどうかをチェックする例を以下に示します。
class Person < ApplicationRecord validates :name, presence: true end
irb> new_person = Person.new irb> new_person.errors[:name] #=> [] # saveするまではバリデーションされないのでエラーにならない irb> new_person.errors[:name].any? #=> false irb> create_person = Person.create irb> create_person.errors[:name] #=> ["can't be blank"] # `name`は必須なのでバリデーションエラーになる irb> create_person.errors[:name].any? #=> true
さらに、errors.addメソッドを使えば、特定の属性のエラーメッセージを手動で追加することも可能です。これは、特にバリデーションシナリオをカスタム定義する場合に便利です。
class Person < ApplicationRecord validate do |person| errors.add :name, :too_short, message: "長さが足りません" end end
より高レベルなバリデーションエラーについては、バリデーションエラーの取り扱いセクションを参照してください。
Active Recordには、クラス定義の内側で直接使える定義済みのバリデーションが多数用意されています。これらの定義済みバリデーションは、共通のバリデーションルールを提供します。
バリデーションが失敗するたびに、オブジェクトのerrorsコレクションにエラーメッセージが追加され、このメッセージはバリデーションが行われる属性に関連付けられます。
バリデーションが失敗すると、バリデーションをトリガーした属性名の下のerrorsコレクションにエラーメッセージを保存します。これにより、特定の属性に関連するエラーに手軽にアクセスできます。たとえば、:name属性のバリデーションが失敗すると、errors[:name]の下にエラーメッセージが保存されるのがわかります。。
最近のRailsアプリケーションでは、以下のように従来よりも簡潔なvalidates構文を使うのが一般的です。
validates :name, presence: true
しかし、古いバージョンのRailsでは以下のvalidates_presence_ofのような「ヘルパー形式の」メソッドが使われていました。
validates_presence_of :name
どちらの記法でも機能は同じですが、読みやすさとRailsの規約との整合性を考えると、validateによる新しい記法が推奨されます。
:onオプションと:messageオプションは新旧両方のバリデーションで使えます。
:onオプションは、バリデーションを実行するタイミングを指定します。
:onオプションには、:createまたは:updateのいずれかを指定できます。
:messageオプションは、バリデーション失敗時にerrorsコレクションに追加するメッセージを指定します。
バリデーションにはそれぞれデフォルトのエラーメッセージが用意されていて、:messageオプションを指定しない場合はデフォルトのメッセージが使われます。
利用可能なデフォルトヘルパーのリストについては、ActiveModel::Validations::HelperMethods APIドキュメントを参照してください。ただしこのAPIドキュメントでは、上述の古い記法が使われています。
以下で、最もよく使われるバリデーションを紹介します。
absenceこのバリデータは、指定された属性が「存在してはならない」ことをバリデーションします。
このバリデータの内部では、属性の値がnilや空白(blank: つまり空文字列""またはホワイトスペースのみで構成される文字列)ではないことのチェックにObject#present?メソッドが使われています。
#absenceは、ifオプションと組み合わせた条件付きバリデーションでよく使われます。
class Person < ApplicationRecord validates :phone_number, :address, absence: true, if: :invited? end
irb> person = Person.new(name: "Jane Doe", invitation_sent_at: Time.current) irb> person.valid? #=> true # absenceバリデーションがパスしたことを表す
関連付けが存在しないことを確認したい場合、関連付けをマッピングするのに使われる外部キーが存在しないかどうかをバリデーションするのではなく、関連付け先のオブジェクト自体が存在しないかどうかをバリデーションする必要があります。
class LineItem < ApplicationRecord belongs_to :order, optional: true validates :order, absence: true end
irb> line_item = LineItem.new irb> line_item.valid? #=> true # absenceバリデーションがパスしたことを表す order = Order.create irb> line_item_with_order = LineItem.new(order: order) irb> line_item_with_order.valid? #=> false # absenceバリデーションが失敗したことを表す
belongs_to関連付けの場合、関連付けが存在することはデフォルトでバリデーションされます。関連付けの存在をバリデーションしたくない場合は、optional: trueを指定してください。
Railsは通常、逆方向の関連付けを自動的に推測します。
カスタムの:foreign_keyや:through関連付けを使う場合は、関連付けの探索を最適化するために:inverse_ofオプションを明示的に指定することが重要です。これにより、バリデーション中に不要なデータベースクエリが発生することを回避できます。
詳しくは、関連付けガイドの双方向関連付けを参照してください。
関連付けが存在することと、関連付けが有効であることを同時に確認したい場合は、validates_associatedも使う必要があります。詳しくはvalidates_associatedで後述します。
has_oneやhas_manyリレーションシップを経由して関連付けられたオブジェクトが存在しないことをabsenceでバリデーションすると、「presence?でもなくmarked_for_destruction?(削除用マーク済み)でもない」かどうかをチェックできます。
false.present?は常にfalseなので、真偽値に対してこのメソッドを使うと正しい結果が得られません。真偽値が存在しないことをチェックしたい場合は、以下のように書く必要があります。
validates :field_name, exclusion: { in: [true, false] }
デフォルトのエラーメッセージは「must be blank」です。
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 agreed to" } end
acceptanceには:acceptオプションも渡せます。このオプションは、「同意可能(acceptable)」とみなす値を指定します。デフォルトは['1', true]ですが、以下のように手軽に変更できます。
class Person < ApplicationRecord validates :terms_of_service, acceptance: { accept: "yes" } validates :eula, acceptance: { accept: ["TRUE", "accepted"] } end
これはWebアプリケーション特有のバリデーションであり、データベースに保存する必要はありません。これに対応するフィールドがなくても、単にヘルパーが仮想の属性を作成してくれます。
このフィールドがデータベースに存在すると、acceptオプションを設定するかtrueを指定しなければならず、そうでない場合はバリデーションが実行されなくなります。
confirmationこのバリデータは、2つのテキストフィールドの入力内容が完全に一致する必要がある場合に使います。
たとえば、メールアドレスやパスワードの確認フィールドも追加するとします。このバリデーションは仮想の属性を作成します。属性の名前は、確認したい属性名に「_confirmation」を追加したものを使います。
class Person < ApplicationRecord validates :email, confirmation: true end
ビューテンプレートで以下のようなフィールドを用意します。
<%= text_field :person, :email %> <%= text_field :person, :email_confirmation %>
このチェックは、email_confirmationがnilでない場合のみ行われます。確認を必須にするには、以下のように確認用の属性について存在チェックも追加してください。presenceを利用する存在チェックについては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」です。
messageオプションでカスタムメッセージを渡すことも可能です。
このバリデータを使う場合は、:ifオプションと組み合わせて、レコードを保存するたびに「_confirmation」フィールドをバリデーションするのではなく、初期フィールドが変更されたときのみバリデーションするのが一般的です。
詳しくは条件付きバリデーションで後述します。
class Person < ApplicationRecord validates :email, confirmation: true validates :email_confirmation, presence: true, if: :email_changed? end
comparisonこのバリデーションは、比較可能な2つの値を比較します。
class Promotion < ApplicationRecord validates :end_date, comparison: { greater_than: :start_date } end
このバリデータのデフォルトのエラーメッセージは「failed comparison」です。
messageオプションでカスタムメッセージを渡すことも可能です。
サポートされているオプションは以下のとおりです。
: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}」です。このバリデータには比較オプションを指定する必要があります。各オプションには値、proc、シンボルを渡せます。RubyのComparableを含む任意のクラスを比較可能です。
formatこのバリデータは、withオプションで与えられた正規表現と属性の値がマッチするかどうかをチェックします。
class Product < ApplicationRecord validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/, message: "英文字のみが使えます" } end
逆に、:withoutオプションを使うと、指定の属性が正規表現にマッチしないことを必須化できます。
どちらの場合も、指定する:withや:withoutオプションは、正規表現か、正規表現を返すprocまたはlambdaでなければなりません。
デフォルトのエラーメッセージは「is invalid」です。
文字列の冒頭や末尾にマッチさせるときは必ず\Aと\zを使い、^と$は、1行の冒頭や末尾にマッチさせる場合に使うこと。\Aや\zを使うべき場合に^や$を使う誤用が頻発しているため、^や$を使う場合はmultiline: trueオプションを渡す必要があります。ほとんどの場合、本当に必要なのは\Aと\zです。
inclusionとexclusionこれらのバリデータは、属性の値が特定のセットに「含まれているか」「含まれていないか」をチェックします。
指定するセットには、任意のenumerableオブジェクト(配列やrange、procやlambdaやシンボルで動的に生成されたコレクションなど)を利用できます。
inclusion: 値がセット内に存在することをチェックする。exclusion: 値がセット内に存在しないことをチェックするどちらの場合も、:inオプションで値のセットを渡せます(エイリアス:withinも利用可能)。
エラーメッセージをカスタマイズするための全オプションについては、messageセクションを参照してください。
enumerableオブジェクトが「数値」や「時間」「日時」のrangeの場合は、バリデーションにRange#cover?メソッドを使い、それ以外の場合はinclude?を使います。
procやlambdaを使うと、バリデーション対象のインスタンスが引数として渡され、動的なバリデーションが可能になります。
inclusionの場合:
class Coffee < ApplicationRecord validates :size, inclusion: { in: %w(small medium large), message: "%{value} のサイズは無効です" } end
exclusionの場合:
class Account < ApplicationRecord validates :subdomain, exclusion: { in: %w(www us ca jp), message: "%{value}は予約済みです" } end
どちらのバリデーションでも、enumerableを返すメソッドを渡すことで動的なバリデーションを実行可能です。
以下はinclusionでprocを渡した場合の例です。
class Coffee < ApplicationRecord validates :size, inclusion: { in: ->(coffee) { coffee.available_sizes } } def available_sizes %w(small medium large extra_large) end end
以下はexclusionでprocを渡した場合の例です。
class Account < ApplicationRecord validates :subdomain, exclusion: { in: ->(account) { account.reserved_subdomains } } def reserved_subdomains %w(www us ca jp admin) end end
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: 属性の長さは、与えられた区間以内でなければなりません。
このオプションの値はrangeでなければなりません。: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を呼ぶようにします。
制約オプションは一度に1つしか利用できませんが、:minimumと:maximumオプションは組み合わせて使えます。
numericalityこのバリデータは、属性に数値のみが使われていることをバリデーションします。デフォルトでは、整数値または浮動小数点数値にマッチします。これらの冒頭に正負の符号がある場合もマッチします。
値として整数のみを許すことを指定するには、:only_integerをtrueに設定します。これにより、属性の値に対するバリデーションで以下の正規表現が使われます。
/\A[+-]?\d+\z/
それ以外の場合は、Floatを用いる数値への変換を試みます。Floatは、カラムの精度または最大15桁を用いてBigDecimalにキャストされます。
class Player < ApplicationRecord validates :points, numericality: true validates :games_played, numericality: { only_integer: true } end
:only_integerのデフォルトのエラーメッセージは「must be an integer」です。
このバリデータには、:only_integerの他に:only_numericオプションも渡せます。これは、値がNumericのインスタンスでなければならないことを指定し、値がStringの場合は値の解析を試みます。
デフォルトでは、numericalityオプションでnil値は許容されません。nil値を許可するにはallow_nil: trueオプションを指定してください。IntegerカラムやFloatカラムでは、空の文字列がnilに変換される点にご注意ください。
オプションが指定されていない場合のデフォルトのエラーメッセージは「is not a number」です。
: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}」です。:in: 渡された範囲に値が含まれていなければならないことを指定します。
デフォルトのエラーメッセージは「must be in %{count}」です。:odd: trueの場合は奇数でなければなりません。
デフォルトのエラーメッセージは「must be odd」です。:even: trueの場合は偶数でなければなりません。
デフォルトのエラーメッセージは「must be even」です。presenceこのバリデータは、指定された属性が空(empty)でないことをチェックします。
このバリデータの内部では、属性の値がnilや空白(blank: つまり空文字列""またはホワイトスペースのみで構成される文字列)であることのチェックにObject#blank?メソッドが使われています。
class Person < ApplicationRecord validates :name, :login, :email, presence: true end
person = Person.new(name: "Alice", login: "alice123", email: "alice@example.com") person.valid? #=> true # presenceバリデーションがパスしたことを示す invalid_person = Person.new(name: "", login: nil, email: "bob@example.com") invalid_person.valid? #=> false # presenceバリデーションが失敗したことを示す
関連付けが存在していることを確認したい場合、関連付けをマッピングするのに使われる外部キーが存在するかどうかをバリデーションするのではなく、関連付け先のオブジェクト自体が存在するかどうかをバリデーションする必要があります。
以下の例では、外部キーが空ではないことと、関連付けられたオブジェクトが存在することをチェックしています。
class Supplier < ApplicationRecord has_one :account validates :account, presence: true end
irb> account = Account.create(name: "Account A") irb> supplier = Supplier.new(account: account) irb> supplier.valid? #=> true # presenceバリデーションがパスしたことを示す irb> invalid_supplier = Supplier.new irb> invalid_supplier.valid? #=> false # presenceバリデーションが失敗したことを示す
カスタムの:foreign_keyや:through関連付けを使う場合は、関連付けの探索を最適化するために:inverse_ofオプションを明示的に指定することが重要です。これにより、バリデーション中に不要なデータベースクエリが発生することを回避できます。
詳しくは、関連付けガイドの双方向関連付けを参照してください。
関連付けが存在することと、関連付けが有効であることを同時に確認したい場合は、validates_associatedも使う必要があります。詳しくはvalidates_associatedで後述します。
has_oneやhas_manyリレーションシップを経由して関連付けられたオブジェクトが存在することをpresenceでバリデーションすると、「blank?でもなくmarked_for_destruction?(削除用マーク済み)でもない」かどうかをチェックできます。
false.blank?は常にtrueなので、真偽値に対してこのメソッドを使うと正しい結果が得られません。真偽値が存在することをチェックしたい場合は、以下のように書く必要があります。
# 値はtrueかfalseでなければならない validates :boolean_field_name, inclusion: [true, false] # 値はnilであってはならない、すなわちtrueかfalseでなければならない validates :boolean_field_name, exclusion: [nil]
これらのバリデーションのいずれかを使うことで、値が決してnilにならないようにできます。nilがあると、ほとんどの場合NULL値になります。
デフォルトのエラーメッセージは「can't be blank」です。
uniquenessこのバリデータは、オブジェクトが保存される直前に、属性の値が一意(unique)であり重複していないことをチェックします。
class Account < ApplicationRecord validates :email, uniqueness: true end
このバリデーションは、その属性と同じ値を持つ既存のレコードがモデルのテーブルにあるかどうかを調べるSQLクエリを実行することで行われます。
一意性チェックの範囲を限定する別の属性を指定する:scopeオプションも利用できます。
class Holiday < ApplicationRecord validates :name, uniqueness: { scope: :year, message: "発生は年に1度である必要があります" } end
このバリデーションはデータベースに一意性制約(uniqueness constraint)を作成しないので、異なる2つのデータベース接続が使われていると、一意であるべきカラムに同じ値を持つレコードが2つ作成される可能性があります。これを避けるには、データベース側でそのカラムにuniqueインデックスを作成する必要があります。
データベースに一意性データベース制約を追加するには、マイグレーションでadd_indexステートメントを使ってunique: trueオプションを指定します。
一意性バリデーションで:scopeオプションを指定し、かつ一意性バリデーション違反を防ぐデータベース制約を作成したい場合は、データベース側で両方のカラムにuniqueインデックスを作成しなければなりません。
詳しくは、MySQLのマニュアルやMariaDBのマニュアルでマルチカラムインデックスについての情報を参照するか、PostgreSQLのマニュアルなどでカラムのグループを参照する一意性制約についての例を参照してください。
:case_sensitiveオプションを指定することで、一意性制約で大文字小文字を区別するかどうか、またはデータベースのデフォルトの照合順序(collation)を尊重すべきかどうかを定義できます。このオプションは、データベースのデフォルト照合順序をデフォルトで尊重します。
class Person < ApplicationRecord validates :name, uniqueness: { case_sensitive: false } end
一部のデータベースでは検索で常に大文字小文字を区別しない設定になっているものがあります。
:conditionsオプションを使うと、一意性制約の探索を制限するための追加条件を以下のようにSQLのWHEREフラグメントとして指定できます。
validates :name, uniqueness: { conditions: -> { where(status: "active") } }
デフォルトのエラーメッセージは「has already been taken」です。
詳しくはAPIドキュメントのvalidates_uniqueness_ofを参照してください。
validates_associated常に有効でなければならない関連付けがモデルにある場合は、このバリデータを使う必要があります。オブジェクトを保存しようとするたびに、関連付けられているオブジェクトごとにvalid?が呼び出されます。
class Library < ApplicationRecord has_many :books validates_associated :books end
このバリデーションは、すべての種類の関連付けで機能します。
validates_associatedを関連付けの両側で使ってはいけません。互いを呼び出して無限ループになります。
validates_associatedのデフォルトのエラーメッセージは「is invalid」です。
各関連付けオブジェクトには、それ自身のerrorsコレクションも含まれることに注意してください。エラーは呼び出し元のモデルには達しません。
validates_associatedはActive Recordオブジェクトでしか利用できませんが、従来のバリデーションはActiveModel::Validationsを含む任意のオブジェクトでも利用できます。
validates_eachこのバリデータは、属性をブロックでバリデーションします。
事前定義されたバリデーション関数を持っていないため、ブロックを扱うバリデーション関数を独自に作成する必要があります。validates_eachに渡されたすべての属性は、そのブロックに対してテストされます。
以下の例は、小文字で始まる名前と姓を却下します。
class Person < ApplicationRecord validates_each :name, :surname do |record, attr, value| record.errors.add(attr, "大文字で始まる必要があります") if /\A[[:lower:]]/.match?(value) end end
このブロックは、レコード(record)、属性名(attr)、属性の値(value)を受け取ります。
任意のコードを書いてブロック内のデータが有効かどうかをチェックできます。バリデーションに失敗した場合は、モデルにエラーを追加することで無効とマーキングする必要があります。
validates_withこのバリデータは、バリデーション専用の別クラスにレコードを渡します。
class AddressValidator < ActiveModel::Validator def validate(record) if record.house_number.blank? record.errors.add :house_number, "省略できません" end if record.street.blank? record.errors.add :street, "省略できません" end if record.postcode.blank? record.errors.add :postcode, "省略できません" end end end class Invoice < ApplicationRecord validates_with AddressValidator end
validates_withにはデフォルトのエラーメッセージがないので、バリデータクラスのレコードのエラーコレクションに、手動でエラーを追加する必要があります。
record.errors[:base]には、そのレコード全体のステートに関連するエラーメッセージを追加するのが一般的です。
バリデーションメソッドを実装するには、メソッド定義内にrecordパラメータが必要です。このパラメータはバリデーションを行なうレコードを表します。
特定の属性に関するエラーを追加したい場合は、以下のようにaddメソッドの第1引数にその属性を渡します。
def validate(record) if record.some_field != "承認可" record.errors.add :some_field, "このフィールドは承認不可です" end end
詳しくはバリデーションエラーで後述します。
validates_withバリデータは、バリデーションに使うクラス(またはクラスのリスト)を引数に取ります。
class Person < ApplicationRecord validates_with MyValidator, MyOtherValidator, on: :create end
validates_withでも他のバリデーションと同様に:if、:unless、:onオプションが使えます。その他のオプションは、バリデータクラスにoptionsとして渡されます。
class AddressValidator < ActiveModel::Validator def validate(record) options[:fields].each do |field| if record.send(field).blank? record.errors.add field, "省略できません" end end end end class Invoice < ApplicationRecord validates_with AddressValidator, fields: [:house_number, :street, :postcode, :country] end
このバリデータは、アプリケーションのライフサイクル内で一度しか初期化されない点にご注意ください。バリデーションが実行されるたびに初期化されることはないため、バリデータ内でインスタンス変数を使う場合は十分な注意が必要です。
作成したバリデータが複雑になってインスタンス変数を使いたくなった場合は、代わりに素のRubyオブジェクトを使う方がやりやすいでしょう。
class Invoice < ApplicationRecord validate do |invoice| AddressValidator.new(invoice).validate end end class AddressValidator def initialize(invoice) @invoice = invoice end def validate validate_field(:house_number) validate_field(:street) validate_field(:postcode) end private def validate_field(field) if @invoice.send(field).blank? @invoice.errors.add field, "#{field.to_s.humanize}は省略できません" end end end
詳しくはカスタムバリデーションで後述します。
これまで見てきたバリデータにはさまざまな共通オプションがあるので、主なオプションを以下に示します。
:allow_nil: 属性がnilの場合にバリデーションをスキップする。:allow_blank: 属性がblankの場合にバリデーションをスキップする。:message: カスタムのエラーメッセージを指定する。:on: このバリデーションを有効にするコンテキストを指定する。:strict: バリデーション失敗時にraiseする。:ifと:unless: バリデーションする場合やしない場合の条件を指定する。一部のバリデータは、これらのオプションをサポートしていません。詳しくはActiveModel::Validations APIドキュメントを参照してください。
:allow_nil:allow_nilオプションは、対象の値がnilの場合にバリデーションをスキップします。
class Coffee < ApplicationRecord validates :size, inclusion: { in: %w(small medium large), message: "%{value}は有効な値ではありません" }, allow_nil: true end
irb> Coffee.create(size: nil).valid? #=> true irb> Coffee.create(size: "mega").valid? #=> false
message:引数の完全なオプションについては、:messageの項を参照してください。
:allow_blank:allow_blankオプションは:allow_nilオプションと似ています。このオプションを指定すると、属性の値がblank?に該当する場合(nilや空文字列""など)はバリデーションがパスします。
class Topic < ApplicationRecord validates :title, length: { is: 6 }, allow_blank: true end
irb> Topic.create(title: "").valid? #=> true irb> Topic.create(title: nil).valid? #=> true irb> Topic.create(title: "short").valid? #=> false # 'short'は長さ6ではないので、blankでなくてもバリデーションは失敗する
:message既に例示したように、:messageオプションを使うことで、バリデーション失敗時にerrorsコレクションに追加されるカスタムエラーメッセージを指定できます。
このオプションを使わない場合、Active Recordはバリデーションヘルパーごとにデフォルトのエラーメッセージを使います。
:messageオプションはStringまたはProcを値として受け取ります。
Stringの:message値には、オプションで%{value}、%{attribute}、%{model}のいずれか、またはすべてを含められます。
これらのプレースホルダは、バリデーションが失敗した場合に動的に置き換えられます。この置き換えにはi18n gemが使われるため、プレースホルダは完全に一致する必要があり、プレースホルダ内にはスペースを含んではいけません。
class Person < ApplicationRecord # メッセージを直書きする場合 validates :name, presence: { message: "省略できません" } # 動的な属性値を含むメッセージの場合。%{value}は実際の属性値に # 置き換えられる。%{attribute}や%{model}も利用可能。 validates :age, numericality: { message: "%{value}は誤りかもしれません" } end
Procの:message値は以下の2つの引数を受け取ります。
:modelと:attributeと:valueのキーバリューペアを含むハッシュclass Person < ApplicationRecord validates :username, uniqueness: { # object = バリデーションされる人物のオブジェクト # data = { model: "Person", attribute: "Username", value: <username> } message: ->(object, data) do "#{object.name}さま、#{data[:value]}は既に入力済みです" end } end
エラーメッセージを翻訳する方法について詳しくは、国際化(I18n)ガイドを参照してください。
: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にコンテキスト名を渡して明示的にトリガーする必要があります。
class Person < ApplicationRecord validates :email, uniqueness: true, on: :account_setup validates :age, numericality: true, on: :account_setup end
irb> person = Person.new(age: 'thirty-three')
irb> person.valid?
#=> true
irb> person.valid?(:account_setup)
#=> false
irb> 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 Book include ActiveModel::Validations validates :title, presence: true, on: [:update, :ensure_title] end
irb> book = Book.new(title: nil)
irb> book.valid?
#=> true
irb> book.valid?(:ensure_title)
#=> false
irb> book.errors.messages
#=> {:title=>["can't be blank"]}
タイミングを明示的に指定したモデルのバリデーションがトリガーされると、そのタイミングを指定したバリデーションに加えて、タイミングを指定していないバリデーションもすべて実行されます。
class Person < ApplicationRecord validates :email, uniqueness: true, on: :account_setup validates :age, numericality: true, on: :account_setup validates :name, presence: true end
irb> person = Person.new
irb> person.valid?(:account_setup)
#=> false
irb> person.errors.messages
#=> {:email=>["has already been taken"], :age=>["is not a number"], :name=>["can't be blank"]}
on:のユースケースについて詳しくは、コールバックガイドで解説します。
特定の条件を満たす場合にのみバリデーションを実行したい場合があります。
このような条件指定は、: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
lambdaはProcの一種なので、lambda記法(->)を用いて以下のようにインライン条件をさらに短く書くことも可能です。
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にならない場合にのみ実行されます。
バリデーションを厳密にし、オブジェクトが無効だった場合にActiveModel::StrictValidationFailedが発生するようにすることもできます。
class Person < ApplicationRecord validates :name, presence: { strict: true } end
irb> Person.new.valid? => ActiveModel::StrictValidationFailed: Name can't be blank
上のように:strictオプションでバリデーションを厳密化すると、バリデーションが失敗したときに即座に例外が発生します。
これは、無効なデータが検出されたら即座に処理を停止する必要がある場合などに役立ちます。たとえば、重要なトランザクションの処理やデータ整合性チェックの実行など、無効な入力によってそれ以上操作を進めないようにする必要があるシナリオでは、厳密なバリデーションが有効です。
以下のようにカスタム例外を:strictオプションに追加することも可能です。
class Person < ApplicationRecord validates :token, presence: true, uniqueness: true, strict: TokenGenerationException end
irb> Person.new.valid? #=> TokenGenerationException: Token can't be blank
指定したオブジェクトのバリデータをすべて調べたい場合は、validatorsでバリデータのリストを表示できます。
たとえば、カスタムバリデータと組み込みバリデータを使った次のようなモデルがあるとします。
class Person < ApplicationRecord validates :name, presence: true, on: :create validates :email, format: URI::MailTo::EMAIL_REGEXP validates_with MyOtherValidator, strict: true end
このとき、以下のようにIRBでvalidatorsを実行してPersonモデルのすべてのバリデータのリストを表示することも、validators_onで特定のフィールド用のバリデータがあるかどうかをチェックすることも可能です。
irb> Person.validators
#=> [#<ActiveRecord::Validations::PresenceValidator:0x10b2f2158
@attributes=[:name], @options={:on=>:create}>,
#<MyOtherValidatorValidator:0x10b2f17d0
@attributes=[:name], @options={:strict=>true}>,
#<ActiveModel::Validations::FormatValidator:0x10b2f0f10
@attributes=[:email],
@options={:with=>URI::MailTo::EMAIL_REGEXP}>]
#<MyOtherValidator:0x10b2f0948 @options={:strict=>true}>]
irb> Person.validators_on(:name)
#=> [#<ActiveModel::Validations::PresenceValidator:0x10b2f2158
@attributes=[:name], @options={on: :create}>]
組み込みのバリデーションだけでは不足の場合、好みのバリデータやバリデーションメソッドを作成して利用できます。
カスタムバリデータは、ActiveModel::Validatorを継承するクラスです。
これらのクラスでは、validateメソッドを実装する必要があります。このメソッドはレコードを1つ引数に取り、それに対してバリデーションを実行します。カスタムバリデータはvalidates_withメソッドで呼び出します。
class MyValidator < ActiveModel::Validator def validate(record) unless record.name.start_with? "X" record.errors.add :name, "名前はXで始まる必要があります" end end end class Person validates_with MyValidator end
個別の属性をバリデーションするカスタムバリデータを追加するには、ActiveModel::EachValidatorを使うのが最も手軽で便利です。
カスタムバリデータクラスを作成するときは、以下の3つの引数を受け取るvalidate_eachメソッドを実装する必要があります。
record: インスタンスに対応するレコードattribute: バリデーション対象となる属性value: 渡されたインスタンスの属性の値class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) unless URI::MailTo::EMAIL_REGEXP.match?(value) record.errors.add attribute, (options[:message] || "is not an email") end end end class Person < ApplicationRecord validates :email, presence: true, email: true end
上の例に示したように、標準のバリデーションとカスタムバリデーションを組み合わせて使うことも可能です。
モデルのステートを確認して、無効な場合にerrorsコレクションにメッセージを追加するメソッドを作成できます。これらのメソッドを作成後、validateクラスメソッドを使って登録し、バリデーションメソッド名を指すシンボルを渡す必要があります。
クラスメソッドごとに複数のシンボルを渡せます。バリデーションは登録されたとおりの順序で実行されます。
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, "はアクティブではありません") unless customer.active? end end
:onについて詳しくは上述の:onセクションを参照してください。
コールバックに対して独自のバリデーションコンテキストをカスタム定義できます。 これは、特定のシナリオに基づいてバリデーションを実行したり、特定のコールバックをグループ化して特定のコンテキストで実行したりする場合に便利です。
カスタムコンテキストがよく使われるシナリオは、ウィザードのように複数のステップを持つフォームがあり、ステップごとにバリデーションを実行する場合です。
たとえば、以下のようにフォームのステップごとにカスタムコンテキストを定義できます。
class User < ApplicationRecord validate :personal_information, on: :personal_info validate :contact_information, on: :contact_info validate :location_information, on: :location_info private def personal_information errors.add(:base, "名前は省略できません") if first_name.blank? errors.add(:base, "年齢は18歳以上でなければなりません") if age && age < 18 end def contact_information errors.add(:base, "メールアドレスは省略できません") if email.blank? errors.add(:base, "電話番号は省略できません") if phone.blank? end def location_information errors.add(:base, "住所は省略できません") if address.blank? errors.add(:base, "市区町村名は省略できません") if city.blank? end end
このような場合、ステップごとにコールバックをスキップする形で実装したくなるかもしれませんが、カスタムコンテキストを定義する方がより構造化できます。
コールバックのカスタムコンテキストを定義するには、:onオプションにコンテキストを指定する形で組み合わせる必要があります(on: :personal_infoなど)。
カスタムコンテキストを定義し終えたら、バリデーションをトリガーするときに、以下のように:personal_infoなどのカスタムコンテキストを指定できます。
irb> user = User.new(name: "John Doe", age: 17, email: "jane@example.com", phone: "1234567890", address: "123 Main St") irb> user.valid?(:personal_info) # => false irb> user.valid?(:contact_info) # => true irb> user.valid?(:location_info) # => false
カスタムコンテキストを使うと、コールバックをサポートする任意のメソッドでバリデーションをトリガーすることも可能になります。
たとえば、以下のようにsaveでバリデーションをトリガーするときに:personal_infoなどのカスタムコンテキストを指定できます。
irb> user = User.new(name: "John Doe", age: 17, email: "jane@example.com", phone: "1234567890", address: "123 Main St") irb> user.save(context: :personal_info) # => false irb> user.save(context: :contact_info) # => true irb> user.save(context: :location_info) # => false
valid?メソッドやinvalid?メソッドでは、有効かどうかという概要しかわかりません。しかしerrorsコレクションにあるさまざまなメソッドを使えば、個別のエラーをさらに詳しく調べられます。
以下は最もよく使われるメソッドの一覧です。利用可能なすべてのメソッドについては、ActiveModel::Errors APIドキュメントを参照してください。
errorserrorsメソッドは、個別のエラーを詳しく掘り下げるときの入り口となります。
errorsメソッドは、すべてのエラーを含むActiveModel::Errorクラスのインスタンスを1つ返します。個別のエラーは、ActiveModel::Errorオブジェクトによって表現されます。
class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end
irb> person = Person.new
irb> person.valid?
#=> false
irb> person.errors.full_messages
#=> ["Name can't be blank", "Name is too short (minimum is 3 characters)"]
irb> person = Person.new(name: "John Doe")
irb> person.valid?
#=> true
irb> person.errors.full_messages
#=> []
irb> person = Person.new
irb> person.valid?
#=> false
irb> person.errors.first.details
#=> {:error=>:too_short, :count=>3}
errors[]errors[]は、特定の属性についてエラーメッセージをチェックしたい場合に使います。指定の属性に関するすべてのエラーメッセージの文字列の配列を返します(1つの文字列に1つのエラーメッセージが対応します)。属性に関連するエラーがない場合は空の配列を返します。
errors[]は、あくまでオブジェクトの個々の属性でエラーが見つかったかどうかをチェックするだけなので、このメソッドが役に立つのは、バリデーションの実行が完了した後だけです(errorsのコレクションにエラーがあるかどうかを調べるだけで、バリデーション自体はトリガーしません)。
errors[]はオブジェクト全体の有効性に関するバリデーションは行わないので、上で説明したActiveRecord::Base#invalid?メソッドとは異なります。
class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end
irb> person = Person.new(name: "John Doe") irb> person.valid? #=> true irb> person.errors[:name] #=> [] irb> person = Person.new(name: "JD") irb> person.valid? #=> false irb> person.errors[:name] #=> ["is too short (minimum is 3 characters)"] irb> person = Person.new irb> person.valid? #=> false irb> person.errors[:name] #=> ["can't be blank", "is too short (minimum is 3 characters)"]
errors.whereとエラーオブジェクトエラーごとに、そのエラーメッセージ以外の情報も必要になることがあります。各エラーはActiveModel::Errorオブジェクトとしてカプセル化されており、それらへのアクセスに最もよく用いられるのがwhereメソッドです。
whereは、さまざまな度合いの条件でフィルタされたエラーオブジェクトの配列を返します。
以下のバリデーションを考えてみましょう。
class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end
errors.where(:attr)の第1パラメータに属性名を渡すと、その属性名だけをフィルタで絞り込めます。
第2パラメータにエラーの種別を渡すと、errors.where(:attr, :type)を呼び出してフィルタで絞り込みます。
irb> person = Person.new irb> person.valid? #=> false irb> person.errors.where(:name) #=> [ ... ] # :name属性のすべてのエラー irb> person.errors.where(:name, :too_short) #=> [ ... ] # :nameの:too_shortエラー
最後の第3パラメータには、指定の型のエラーオブジェクトに存在する可能性のある任意のオプションを指定してフィルタで絞り込めます。
irb> person = Person.new irb> person.valid? #=> false irb> person.errors.where(:name, :too_short, minimum: 3) #=> [ ... ] # 最小が3で短すぎるすべてのnameのエラー
これらのエラーオブジェクトから、さまざまな情報を読み出せます。
irb> error = person.errors.where(:name).last irb> error.attribute => :name irb> error.type => :too_short irb> error.options[:count] => 3
エラーメッセージを生成することも可能です。
irb> error.message #=> "is too short (minimum is 3 characters)" irb> error.full_message #=> "Name is too short (minimum is 3 characters)"
full_messageメソッドは、属性名の冒頭を大文字にした読みやすいメッセージを生成します。
full_messageで使うフォーマットをカスタマイズする方法については、国際化(i18n)ガイドを参照してください
errors.addaddメソッドを使って、特定の属性に関連するエラーメッセージを手動で追加できます。このメソッドは、属性とエラーメッセージを引数として受け取ります。
addメソッドは、「属性名」「エラー種別」「オプションの追加ハッシュ」を受け取ってエラーオブジェクトを作成します。非常に具体的なエラー状況を定義できるため、独自のバリデータを作成するときに便利です。
class Person < ApplicationRecord validate do |person| errors.add :name, :too_plain, message: "はあまりクールじゃない" end end
irb> person = Person.create irb> person.errors.where(:name).first.type #=> :too_plain irb> person.errors.where(:name).first.full_message #=> "Nameはあまりクールじゃない"
errors[:base]特定の属性に関連するエラーではなく、オブジェクト全体の状態に関連するエラーを追加できます。
これを行うには、新しいエラーを追加するときに属性として:baseを指定する必要があります。
class Person < ApplicationRecord validate do |person| errors.add :base, :invalid, message: "この人物は以下の理由で無効です: " end end
irb> person = Person.create irb> person.errors.where(:base).first.full_message #=> "この人物は以下の理由で無効です: "
errors.sizesizeメソッドは、オブジェクトのエラーの全件数を返します。
class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end
irb> person = Person.new irb> person.valid? #=> false irb> person.errors.size #=> 2 irb> person = Person.new(name: "Andrea", email: "andrea@example.com") irb> person.valid? #=> true irb> person.errors.size #=> 0
errors.clearclearメソッドは、errorsコレクションに含まれるメッセージをすべてクリアしたい場合に使えます。無効なオブジェクトに対してerrors.clearメソッドを呼び出しても、オブジェクトが実際に有効になるわけではありませんのでご注意ください。
errorsは空になりますが、valid?やオブジェクトをデータベースに保存しようとするメソッドが次回呼び出されたときに、バリデーションが再実行されます。そしていずれかのバリデーションが失敗すると、errorsコレクションに再びメッセージが保存されます。
class Person < ApplicationRecord validates :name, presence: true, length: { minimum: 3 } end
irb> person = Person.new irb> person.valid? #=> false irb> person.errors.empty? #=> false irb> person.errors.clear irb> person.errors.empty? #=> true irb> person.save #=> false irb> person.errors.empty? #=> false
モデルを定義してバリデーションを追加したら、Webフォームでそのモデルを作成中にバリデーションが失敗したときに、ユーザーにエラーメッセージを表示する必要があります。
エラーメッセージの表示方法はアプリケーションごとに異なるため、そうしたメッセージを直接生成するビューヘルパーはRailsに含まれていません。 しかし、Railsでは一般的なバリデーションメソッドが多数提供されているので、それらを活用してカスタムのメソッドを作成できます。
また、生成をscaffoldで行なうと、そのモデルのエラーメッセージをすべて表示するERBがRailsによって自動的に_form.html.erbファイルに追加されます。
@articleという名前のインスタンス変数に保存されたモデルがあるとすると、ビューは以下のようになります。
<% if @article.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(@article.errors.count, "error") %>が原因でこの記事を保存できませんでした</h2> <ul> <% @article.errors.each do |error| %> <li><%= error.full_message %></li> <% end %> </ul> </div> <% end %>
また、フォームをRailsのフォームヘルパーで生成した場合、あるフィールドでバリデーションエラーが発生すると、そのエントリの周りに追加の<div>が自動的に生成されます。
<div class="field_with_errors"> <input id="article_title" name="article[title]" size="30" type="text" value=""> </div>
この<div>タグに好みのスタイルを追加できます。Railsがscaffoldで生成するデフォルトのCSSルールは以下のようになります。
.field_with_errors {
padding: 2px;
background-color: red;
display: table;
}
このCSSは、バリデーションエラーが発生したフィールドを太さ2ピクセルの赤い枠で囲みます。
Railsは、エラーが表示されているフィールドをfield_error_proc設定オプションを用いてHTMLでラップします。
このオプションは、デフォルトでは上述の例に示すように、エラーが表示されているフォームフィールドをfield_with_errors CSSクラスで<div>要素にラップします。
config.action_view.field_error_proc = Proc.new { |html_tag, instance| content_tag :div, html_tag, class: "field_with_errors" }
フォームでのエラーの表示スタイルは、アプリケーションのfield_error_proc設定を変更してこの振る舞いをカスタマイズすることで変更できます。詳しくは設定ガイドのfield_error_procを参照してください。
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。