久々の失敗談エントリです。素人かよって言われるレベルですがよろしければこのままお進みください。
前提
Model
こんな Model クラスがあったとします。
class Some < ApplicationRecord
validates :kind, length: { maximum: 4 }, unless: :skip_foo_validation
attr_accessor :skip_foo_validation
end
Controller
で、コントローラでこんなかんじに Some のレコードを作成します。
record = Some.find_or_create_by(kind: 'some_kind')
今回の状況
別の用途があってここで作成したレコードの主キー(Some では自動採番による id が主キーであるとします。)をすぐ確認したいとします。
puts record.id # => nil
nil...だと...?
Some.find_by(kind: 'some_kind') #=> nil
DBにもまだ保存されていません。あれ、record.save とか呼ばなきゃ保存までされないんでしたっけ?そんなはずはないんだが。。。( ゚д゚)
ここでクエスチョン
Q. record.id が nil になる理由と対応方法を回答してください。
ということでさあ困りました。これはどういうことなんでしょうか。
回答例
まあ大したオチはないんですが、バリデーションがまだ通っていないためレコードのINSERTが行われていないことが理由です。 find_byでレコードがなかった時点でINSERTされてないのはわかりましたが理由が謎でした。
何故判明したかといいますと、errors に思いっきりバリデーションのエラー内容が出ていたためです。 我ながらひどい。(細かいところは省略してます)
puts record.errors.inspect
# => #<ActiveModel::Errors:**********
# @base=#<Some id: nil, kind: 'some_kind', *****>,
# @details={:kind=>[{:error=>:too_long, :count=>4}]},
# @details={:kind=>["4文字以内で入力してください。"]}
# >
ということで入力内容の問題ということがわかりました。
バリデーションが通るような値を kind に渡せばいいんですが、今回は find_or_create_by にブロックを渡してスキップ用の値をセットする方向で対処してみます。
record = Some.find_or_create_by(kind: :some_kind) do |rec|
rec.skip_foo_validation = true
end
こんどは大丈夫ですね。
puts record.id # => 1
教訓
例えばチームで開発していて、該当の Model クラスを自分以外の人が作っている場合は自分の知らない validates が含まれているケースは多々あると思います。保存処理を行いたい Modelクラスの validates はひととおり目を通しておくといいとおもいます。(ブーメラン)
今回は find_or_create_by
使用していましたが、特にこのメソッドに限った話ではないですしね。
※ちなみにぶっちゃけ30分以上ハマりました/(^o^)\