- 今回はRails v5.0.2を使用しています
Railsでmodelにバリデーションをつけるがこの時だけはoffにしたいという時がある。
例えば特定のバッチ走らせる時とか?
例えば今回は下記のようなバリデーションを定義していたとして
class Item < ApplicationRecord # nameがユニークかつ文字数が1から10文字内におさまっているか validates :name, uniqueness: true, length: { in: 1..10 } end
この Item
モデルのバリデーションをすべてOFFにするなら
item = Item.new(name: 'hello!') item.save!(validate: false)
これでいける
ただ特定の条件だけ外したい場合はどうだろう? 今回の例でいうとユニークは保ちたいから文字数のチェックだけを外したい場合
今回はこのようにモデルを変更した
class Item < ApplicationRecord validates :name, uniqueness: true validates :name, length: { in: 1..10 }, unless: -> { validation_context == :hoge } end
このように定義しなおして rails console
などで試す
irb> Item.new(name: 'a' * 100).save!(context: :hoge) true irb> Item.new(name: 'a' * 100).save!(context: :hoge) ActiveRecord::RecordInvalid: Validation failed: Name has already been taken
一回目は文字数制限を外した状態でレコードは保存され、もう一度同じレコードを生成しようとするとユニークでないのでエラーになる!
意図した動きをしてくれるようになった
何が置きたのか?
railsのソースを追ってみた
save!(context: :hoge)
とcallすると下記が呼ばれる
# https://github.com/rails/rails/blob/v5.0.2/activerecord/lib/active_record/validations.rb#L50 def save!(options={}) perform_validations(options) ? super : raise_validation_error end
perform_validations(options)
とやらがfalseをかえすとraiseでvalidation errorが出る
perform_validations(options)
はすぐ下に定義されていて
# https://github.com/rails/rails/blob/v5.0.2/activerecord/lib/active_record/validations.rb#L82 def perform_validations(options={}) # :nodoc: options[:validate] == false || valid?(options[:context]) end
なるほど! save!(validate: false)
とした時はここで trueになるのでvalidationを評価せずに保存できるのか
今回は save!(context: :hoge)
なので valid?
に :hoge
がわたる
このvalid?は同じクラス内に定義されている
# https://github.com/rails/rails/blob/v5.0.2/activerecord/lib/active_record/validations.rb#L63 def valid?(context = nil) context ||= default_validation_context output = super(context) errors.empty? && output end
ここはnilガードとかしてcontextに値をつめたりしてるだけで実際の処理は superで親の ActiveModel
の valid?
が呼び出されている
# https://github.com/rails/rails/blob/v5.0.2/activemodel/lib/active_model/validations.rb#L335 def valid?(context = nil) current_context, self.validation_context = validation_context, context errors.clear run_validations! ensure self.validation_context = current_context end
ここで self.validation_context
に引数でわたってきたcontextをつめているからmodelで validation_context
が参照できたようです