Railsのバリデーションは、:onオプションをつけることで任意のコンテキストのときだけ実行させることができます。
validates :name, presence: true # 常にバリデーションする
validates :description, presence: true, on: :hoge # context: :hogeのときだけバリデーションする
しかし、context: :hoge以外のときだけバリデーションしたいときはどうすればいいのでしょうか。:onで:hoge以外のコンテキストを全て指定するなどというのは流石にありえませんが、Railsガイドには説明がありません。
そのようにしたい場合は、以下のようにvalidation_contextを参照したlambdaやProcを:unlessに渡して条件を指定すれば実現できます。
validates :name, presence: true # 常にバリデーションする
validates :description, presence: true, unless: -> { validation_context == :hoge } # context: :hoge以外のときだけバリデーションする
なぜこのような書き方になるのでしょうか。
まず:unlessで指定できるのは、以下のように:onを:ifに読み替えており:onと:ifは同等、:unlessは::ifの逆なので:unlessは:onの逆となるためです。
# https://github.com/rails/rails/blob/5-2-stable/activemodel/lib/active_model/validations.rb#L154
def validate(*args, &block)
options = args.extract_options!
if args.all? { |arg| arg.is_a?(Symbol) }
options.each_key do |k|
unless VALID_OPTIONS_FOR_VALIDATE.include?(k)
raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?")
end
end
end
if options.key?(:on)
options = options.dup
options[:on] = Array(options[:on])
options[:if] = Array(options[:if])
options[:if].unshift ->(o) {
!(options[:on] & Array(o.validation_context)).empty?
}
end
set_callback(:validate, *args, options, &block)
end
またvalidation_contextを参照できるのは、saveする際に呼ばれる以下の処理において、引数から渡ってきた:contextをself.validation_contextにセットしているためです。
# https://github.com/rails/rails/blob/5-2-stable/activemodel/lib/active_model/validations.rb#L336
def valid?(context = nil)
current_context, self.validation_context = validation_context, context
errors.clear
run_validations!
ensure
self.validation_context = current_context
end
Railsでこの時だけ特定のバリデーションを検証したくないときしたこと
Rails のバリデーションを特定のコンテキスト「以外」で実行させる
なお条件付きバリデーションをwith_optionsでグループ化する記法がありますが、ここでも同様に、以下のようにvalidation_contextを参照して特定のコンテキスト以外の条件をグループ化することができます。
with_options unless -> { validation_context == :hoge } |not_hoge| # context: :hoge以外のときだけバリデーションする
not_hoge.validates :name, presence: true
not_hoge.validates :description, presence: true
end