Railsで特定のコンテキスト「以外」のときだけバリデーションする

Railsのバリデーションは、:onオプションをつけることで任意のコンテキストのときだけ実行させることができます。

validtes :name, presence: true # 常にバリデーションする
validtes :description, presence: true, on: :hoge # context: :hogeのときだけバリデーションする

しかし、context: :hoge以外のときだけバリデーションしたいときはどうすればいいのでしょうか。:on:hoge以外のコンテキストを全て指定するなどというのは流石にありえませんが、Railsガイドには説明がありません。

そのようにしたい場合は、以下のようにvalidation_contextを参照したlambdaやProcを:unlessに渡して条件を指定すれば実現できます。

validtes :name, presence: true # 常にバリデーションする
validtes :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する際に呼ばれる以下の処理において、引数から渡ってきた:contextself.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
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.