ActiveRecordのEachValidatorを複製して狙ったカラムのみバリデーションする。
無職はじめての本格的な平日一週間を過ごして体が慣れてきた。
目的
フォームなどのリアルタイムバリデーションで、ActiveRecordに設定したバリデーターを使いたいがvalid?
で全部やってしまうのはなんだか乱暴な気がしたので、必要なカラムだけでやりたかった。
前提となるモデル
class Model < ActiveRecord::Base
validates :str, :txt, :bol,
presence: true
validate :validate_str
def validate_str
errors.add(:str, :validate_str) if str == '失敗する'
end
end
バリデーターを得る
Model.validators
=> [#<ActiveRecord::Validations::PresenceValidator:0x007fbd8cc96c28 @attributes=[:str, :txt, :bol], @options={}>]
Model._validators
=> {:str=>[#<ActiveRecord::Validations::PresenceValidator:0x007fbd8cc96c28 @attributes=[:str, :txt, :bol], @options={}>], :txt=>[#<ActiveRecord::Validations::PresenceValidator:0x007fbd8cc96c28 @attributes=
[:str, :txt, :bol], @options={}>], :bol=>[#<ActiveRecord::Validations::PresenceValidator:0x007fbd8cc96c28 @attributes=[:str, :txt, :bol], @options={}>]}
カラムごとのバリデーターを利用するには_validators
の方が取り回しが良さそうなのでそっちを使う。
バリデーターの使い方
validator.validate(record)
なのでゴー
validator = Model._validators[:str].first
model = Model.new
validator.validate(model)
model.errors.messages
=> {:str=>["can't be blank"], :txt=>["can't be blank"], :bol=>["can't be blank"]}
str
だけ検査したいんだけど、設定時にまとめてるから一緒にやってくれてしまう。特定のカラムだけ検査したいという欲求なので、今回の場合はうれしくない。
ActiveRecord::Validations::PresenceValidator
の@attributes
を増減させることで対称を絞ることができるが、3カラムすべて同じActiveRecord::Validations::PresenceValidator
のインスタンスを見ているので、よくない。
単一カラム用バリデーターとして複製する
validates
で使われるEachValidator
の初期化はこうなっている。
def initialize(options)
@attributes = Array(options.delete(:attributes))
raise ArgumentError, ":attributes cannot be blank" if @attributes.empty?
super
check_validity!
end
ので、@attributes
に目的のカラムのみが入るようにして作成しなおす。
only_str = validator.class.new(validator.options.merge(attributes: :str))
=> #<ActiveRecord::Validations::PresenceValidator:0x007f09150060e8 @attributes=[:str], @options={}>
model = Model.new
only_str.validate(model)
model.errors.messages
=> {:str=>["can't be blank"]}
validate
のは無視される
model = Model.new(str: '失敗する')
only_str.validate(model)
model.errors.messages
=> {}
validate
で付与されるバリデーションはある種のコールバックであり、カラムに特定されていないのでこの方法では無視される。
valid?
で乱暴に検査をするのもいいが、これを機にEachValidator
化してみてはいかがでしょうか。(今日はslim検査したくてmmmpa/slim_validationとか作ってた)