Railsの Custom Validation
RailsのCustom Validationを初めて使ったから,まとめる。参考資料はRailsガイド。
Record ごとのValidation
以下のHuman Classを考える。ここでいうrecordとは,Human のオブジェクトをを指す。
iclass Human include ActiveModel::Validations attr_reader :first_name, :last_name def initialize(first_name:, last_name:) @first_name = first_name @last_name = last_name end end
Human classに対して,以下のCustom Validation クラスを定義する。
class HumanValidator < ActiveModel::Validator def validate(record) if options[:fields].any? { |field| record.send(field) == "Taro" } record.errors.add(:base, "これは太郎だ") end if options[:first_name] == 'Hanako' record.errors.add(:base, 'これは花子だ') end if options[:initial_value] == 'Jiro' record.errors.add(:base, 'これは次郎だ') end end end
そして,Humanクラスでは,以下のように,Custom Validation を呼び出す。
class Human 略 validates_with HumanValidator, fields: [:first_name, :last_name], first_name: @first_name, initial_value: 'Jiro' end
validates_with
メソッドで,呼び出したいValidation Class を書き,options で,好きなhashを定義でき,Custom Validation のクラスで,options[:initial_value]
のように呼び出せる。
バリデーションを一つずつ確認していく。まずは一つ目。
if options[:fields].any? { |field| record.send(field) == "Taro" } record.errors.add(:base, "これは太郎だ") end
irb(main):008:0> human = Human.new(first_name: 'Taro', last_name: 'Yamada') => #<Human:0x00007fe5b6031d28 @first_name="Taro", @last_name="Yamada"> irb(main):009:0> human.valid? => false irb(main):010:0> human.errors.full_messages => ["これは太郎だ", "これは次郎だ"]
record.send(field) == "Taro"
で,first_name,last_name
に'Taro'
という文字列が存在するかのバリデーションを行い,今回は存在するため,エラーメッセージがHuman オブジェクトに格納されている。
二つ目のバリデーション。
if options[:first_name] == 'Hanako' record.errors.add(:base, 'これは花子だ') end
validates_with
にoptionsとして,first_name: :first_name
を指定している。この時のエラーメッセージは以下となる。
irb(main):011:0> human = Human.new(first_name: 'Hanako', last_name: 'Yamada') => #<Human:0x00007fe5b23bddb0 @first_name="Hanako", @last_name="Yamada"> irb(main):012:0> human.valid? => false irb(main):013:0> human.errors.full_messages => ["これは次郎だ"]
これは花子だ
のエラーが格納されそうだが,されていない。Railsガイドには以下のように書いてある。
このバリデータは、アプリケーションのライフサイクル内で一度しか初期化されない点にご注意ください。バリデーションが実行されるたびに初期化されることはありません。インスタンス変数の扱いには十分ご注意ください。
つまり,validates_with
のoptionsとして加えたfirst_name: @first_name
の@first_nameは初期化時点ではnilであるため,Humanクラスのinitialize時の@first_name = Hanako
は使われることはない。以下のバリデーションをHumanValidator
に追加してみる。
if options[:first_name] == nil record.errors.add(:base, 'nilが初期値です') end
そして,もう一度Humanインスタンスを作成すると,
irb(main):001:0> human = Human.new(first_name: 'Hanako', last_name: 'Yamada') => #<Human:0x00007ff03a085b10 @first_name="Hanako", @last_name="Yamada"> irb(main):002:0> human.valid? => false irb(main):003:0> human.errors.full_messages => ["これは次郎だ", "nilが初期値です"]
のように,nilが初期値です
のエラーが格納されており,アプリケーションの初期化時点では@first_name
がnilとなっていることがわかる。
最後のValidationは,
if options[:initial_value] == 'Jiro' record.errors.add(:base, 'これは次郎だ') end
initial_value
にJiro
を渡しているため,想定通りのエラーが常に格納されている。
AttributeごとのValidation
HumanクラスのAttribute,今回の場合は,first_name, last_name
に対してValidationを追加する。
最初にfirst_name
attributeに対してValidationを追加する。Custom Validationは以下となる。
class HumanFirstNameValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors.add(attribute, 'が次郎です') if value == 'Jiro' end end
Humanクラスで以下のように呼び出す。
class Human 略 validates :first_name, human_first_name: true end
irb(main):009:0> human = Human.new(first_name: 'Jiro', last_name: 'Yamada') => #<Human:0x00007fc5036e0328 @first_name="Jiro", @last_name="Yamada"> irb(main):010:0> human.valid? => false irb(main):011:0> human.errors.full_messages => ["First name が次郎です"]
first_name
に対して,Custom Validationがかけられていることが確認できた。
last_name
に対しても,以下のように同様にValidationを追加することができる。
class HumanLastNameValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors.add(attribute, 'が山田です') if value == 'Yamada' end end
class Human 略 validates :first_name, human_first_name: true validates :last_name, human_last_name: true end
irb(main):016:0> human = Human.new(first_name: 'Hanako', last_name: 'Yamada') => #<Human:0x00007fc507b880c0 @first_name="Hanako", @last_name="Yamada"> irb(main):017:0> human.valid? => false irb(main):018:0> human.errors.full_messages => ["Last name が山田です"]
Attributeに対するCustom Validationは,emailやURLのフォーマットチェックといった少し複雑な処理だが,汎用的なAttributeに対して使用するのが主な目的となる。