【パーフェクトRails】バリデーションをクラスに分離する
目的
パーフェクトRuby on Railsの9章の9−2「複雑なバリデーションとコールバックを整理する」を読んでいて 「バリデーションをクラスに分離する」について、知らないことがいくつかあったのでまとめます。
- 作者: すがわらまさのり,前島真一,近藤宇智朗,橋立友宏
- 出版社/メーカー: 技術評論社
- 発売日: 2014/06/06
- メディア: 大型本
- この商品を含むブログ (8件) を見る
バリデーションをクラスに分離するのは必要?
メールとか氏名とか、プロジェクトで要件が共通になると思うので積極的にやって良いと思う。
Open Source Rails でもapp/validators
は結構見かける。
どうやってやるのか?
例によってサンプルを見るのがわかりやすいと思います。
EachValidatorを使う
# app/validators/email_validator.rb class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors.add(attribute, 'is not an email') unless value =~ /.+@.+/ end end
上記の通りActiveModel::EachValidator
を継承してvalidate_each
を定義すればいい。
呼ぶときは以下のように呼ぶ。
class Person include ActiveModel::Validations attr_accessor :email, :name validates :email, email: true end
validates :email, email: true
は「emailというフィールドに対して、EmailValidator#validate_eachでチェックしてください。」というような解釈で良いと思う。EmailValidator
を見ると引数をがどのように渡ってくるかがわかると思う。
Validatorを使う
# app/validators/multi_presence_validator.rb class MultiPresenceValidator < ActiveModel::Validator def validate(record) record.errors.add(:base, 'bust be presence') if options[:attributes].all? { |c| record.send(c).blank? } end end
ActiveModel::Validator
を継承してvalidate
メソッドを定義する。
呼び出し方は以下。
class Person include ActiveModel::Validations attr_accessor :email, :name validates_with MultiPresenceValidator, attributes: [:email, :name] end
validates_with MultiPresenceValidator
は見たとおりMultiPresenceValidator#validateでバリデーションをする。
attributes: [:email, :name]
という引数に関しては、ActiveModel::Validator
のインスタンスからoptions[:attributes]
とすればさわれるようになっている。
validates_with Validator1, Validator2
と書くと両方のバリデーションを付け加えることができる
http://apidock.com/rails/ActiveModel/Validations/ClassMethods/validates_with
EachValidatorとValidatorの違い
EachValidator
は、上のサンプルの呼び出し方にある通り、1つのフィールドをチェックするやりかた(もちろん複数のフィールドをチェックするように書くことも出来なくはない)になる。
Validator
は複数のフィールドをチェックするのに使う。(こちらも1つのフィールドだけをチェックチェックするのは問題なく出来る。)
EachValidator
はValidator
のサブクラスになっている。
その他
これらの自作バリデーションや標準のバリデーションに関しては、モデルクラスごとにオブジェクトを持つ。インスタンス単位ではない。したがって、状態を持たないようにすること。
まとめ
app/valiadtors
ディレクトリはわりと一般的になっているので、プロジェクトたちあげのときに追加してバリデーションのクラス化を促すのが良いと思う。1フィールドのときは、
ActiveModel::EachValidator
を継承してvalidate_each
メソッド定義複数フィールドのときは、
ActiveModel::Validator
を継承してvalidate
メソッド定義PresenceValidatorsの実装とか読むといいと思う