Active Record バリデーション | Rails ガイド
クライアント側でのバリデーションは扱いやすく便利ですが、一般に単独では信頼性が不足します。JavaScriptを使用してバリデーションを実装する場合、ユーザーがJavaScriptをオフにしてしまえばバイパスされてしまいます。ただし、他の方法と併用するのであれば、クライアント側でのバリデーションはユーザーに即座にフィードバックを返すための便利な方法となるでしょう。 コントローラレベルのバリデーションは一度はやってみたくなるものですが、たいてい手に負えなくなり、テストも保守も困難になりがちです。アプリケーションの寿命を永らえ、保守作業を苦痛なものにしないようにするためには、コントローラのコード量は可能な限り減らすべきです。
耳が痛い
Railsチームは、ほとんどの場合モデルレベルのバリデーションが最も適切であると考えています。
バリデーションが実行されるメソッド
以下のメソッドではバリデーションがトリガされ、オブジェクトが有効な場合にのみデータベースに保存されます。
- create
- create!
- save
- save!
- update
- update!
- new では、バリデーションは実行されない
save(validate: false)
で、バリデーションをスキップできる!
#valid?, #invalid?
#valid?
: バリデーションがOKならtrue
#invalid?
: バリデーションがNGならtrue
バリデーション後は、errors.messages
を参照すると、発生したエラーにアクセスできる
user = User.new user.valid? # => false user.errors.messages # => {name:["空欄にはできません"]}
バリデーションヘルパ
validates_associated
関連づけられているモデルのバリデーションも同時に実行する
class Library < ActiveRecord::Base has_many :books validates_associated :books end
confirmation
- 2つのテキストフィールドの値が一致しているか確認
- パスワードとかメールアドレスとか
form画面で、以下のように、email と、email_confirmation を用意する
<%= text_field :person, :email %> <%= text_field :person, :email_confirmation %>
validates :email, confirmation: true # confirmation は、email_confirmation が空の場合チェックをしないので、 # email_confirmation 側で必須チェックを行う必要がある # allow_blank: false じゃダメなのかな? validates :email_confirmation, presence: true
そのほか
# acceptance # # チェックボックスがONになっているか確認 # 利用条項を確認しました。とかそんなのに使う validates :terms_of_service, acceptance: true # inclusion, exclusion # # 特定の値であることを確認(ラジオボタンの選択値のチェックとか) validates :size, inclusion: { in: %w(small medium large) } # 特定の値でないことを確認 validates :subdomain, exclusion: { in: %w(www us ca jp) } # format # # 正規表現と一致するか確認 validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/ } # length # # 文字長を確認 validates :name, length: { minimum: 2 } # 最小 validates :bio, length: { maximum: 500 } # 最大 validates :password, length: { in: 6..20 } # 範囲 validates :registration_number, length: { is: 6 } # 固定 # numericality # # 数値のみであるか確認 validates :points, numericality: true validates :games_played, numericality: { only_integer: true } # 小数点禁止 # 他のオプション # - greater_than, greater_than_or_equal_to # - equal_to # - less_than, less_than_or_equal_to # - even, odd # presence, absence # # 必須チェック(#blank? メソッドを呼ぶ) validates :name, :login, :email, presence: true # その逆(#present? メソッドを使う) validates :name, :login, :email, absence: true # false.blank? は true なので、真偽値のチェックは inclusion を使う validates :field_name, inclusion: { in: [true, false] } # uniqueness # # ユニークチェック # より厳密にやるには、DB側でもuniq制約をつけること validates :email, uniqueness: true # 複数カラムの組み合わせでユニークチェック validates :email, uniqueness: { scope: :year } # 大文字小文字は区別しない validates :email, uniqueness: { case_sensitive: false }
validates_each
複数のカラムに対して、独自のバリデーションを実行する
class Person < ActiveRecord::Base 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 : nilの場合バリデーションを行わない
- allow_blank : blankの場合バリデーションを行わない
ruby # 何も入力していない時に、必須チェックエラーと、文字数チェックエラー両方を出さないようにする # 必須だけれど、何も入力しない時は、文字数チェックを実施しない validates :name, persence:true, length: { maximum: 256, allow_blank: true }
- message : エラーメッセージ
- strict : バリデーション失敗時例外を発生させる
on オプション
バリデーションの実行タイミングを指定
on: :create
: 新規作成時のみon: :update
: 更新時のみ
独自のコンテキストを設定することも可能
validates :username, presence: true, on: :registration
呼び出し
@user.save(context: :registration) @user.valid?(:registration)
条件付きバリデーション
if, unless オプションの条件を満たす時にバリデーションを実行する
# メソッド呼び出し validates :card_number, presence: true, if: :paid_with_card? def paid_with_card? payment_type == "card" end # eval validates :surname, presence: true, if: "name.nil?" # proc validates :password, confirmation: true, unless: ->(a){ a.password.blank? } # with_optionsでグループ化 with_options if: :is_admin? do |admin| admin.validates :password, length: { minimum: 10 } admin.validates :email, presence: true end # 複数条件指定 validates :mouse, presence: true, if: [:retail?, :desktop?] unless: Proc.new { |c| c.trackpad.present? }
カスタムバリデータ
# ActiveModel::Validator を継承。#validate を実装 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 # validates_with で呼び出す end
個別の属性を検証する
# ActiveModel::EachValidator を継承。#validate_each を実装する 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 < ActiveRecord::Base validates :email, presence: true, email: true end
#errors
- エラー情報を格納
- キー : 属性名、値 : エラー文字列の配列
# エラーメッセージの追加 errors.add(:mail, 'メールアドレスが不正') errors[:mail] = 'メールアドレスが不正' errors[:base] # 個別の属性にかかわらない、オブジェクト全体のエラーを持つ errors.clear # エラーメッセージのクリア