railsでネストされた小モデルに対してバリデーションしようと思った時に苦しんだお話
掲題のようなことをやろうとして大いにハマったので、解決方法を書いておく(ちなみに、やりたいことは実現できたというだけなので、こうすると楽だよ、的なご指摘あればコメントしていただけると大変嬉しいです。)
まず今回やりたかったこと(機能的には投稿の一時保存的なものをつくろうとしていました)
・accepts_nested_attributes_for :fugafuga
のようにネストされた小モデルのバリデーションをしたい
・あるアクション(僕の場合は一時保存アクション)から呼ばれた場合のみ、presence: true
だけ無視したい(その他にもlengthのチェックなどをしている)
1. on: :hogehoge の逆が簡単にできない
特定のアクションの場合のみバリデーションをしたいときは on: :hogehoge
のようにvalidation_cotext
をonオプションで指定してやってやるのが便利なんだけど、今回は特定のアクション以外の場合という条件分岐をしたかったので結構手こずった。
一時保存以外のアクションの場合(今回はcreateとupdate)にonオプションをつけても実現できるのだが、
with_options on: [:create, :update] do |hogehoge|
hogehoge.validates :fuga, presence: true
end
上記のようにしてしまうと、今後このモデルを扱うアクションが増えた時なんかにメンテナンスがしににくなりそう(というか本質的にやりたいことではないのでなんか気持ち悪い)なので、なんとか一時保存のアクションでなければ
という条件分岐を作ってやりたかった。
そしたらちょうど同じことで悩んでいる方の記事を発見
http://labs.timedia.co.jp/2014/07/rails-validation-not-on-context.html
こちらにならい、ブロックをunlessに渡してやることにより一旦この問題は解決
with_options unless: -> { [:draft].include?(validation_context) } do |offer|
offer.validates :position, presence: true
end
こんな感じ
2. ネストされた小モデルにvalidation_contextが渡らない
親モデルは上の方法でうまくいったのだが、accepts_nested_attributes_for :fugafuga
のようにネストされた小モデルに対してはうまく行かなかった。
いろいろ調べてみると、どうやらvalidation_context
がうまく渡っていないご様子。一時保存のアクションからmodel.save
をしても親モデルには渡っているが、ネストされた小モデルのvalidation_context
にはupdate
or create
が渡ってしまっていた。
どのようにcontextを渡しているのかを調べてみると、以下のようにcontextがnilの場合は、新規レコードかどうかを見てcreateかupdateをつっこんでいるらしい。
#activerecord/lib/active_record/validations.rb
def valid?(context = nil)
context ||= (new_record? ? :create : :update)
output = super(context)
errors.empty? && output
end
親モデルに対してcontextを指定してあげてもaccepts_nested_attributes_for
でネストした子モデルにはnilが渡っているっぽい。
結構いろいろ試したんだけど、どうも子モデルのバリデーションにcontextを渡すことが出来ず。
以下のように親モデルの中で無理やりバリデーションさせることで対応した。
with_options unless: -> { [:draft].include?(validation_context) } do |offer|
offer.validates :position, presence: true
# 追加部分
offer.validates :fugafuga_name, presence: true
end
private
def fugafuga_name
fugafuga.name
end
うーん、一応やりたいことは実現できたのだが、、、
そもそもRailsが用意していないことをやろうとしている時点で、何かがおかしいですかね、、、