accepts_nested_attributes_forにおいて、最初のデータの保存がうまくいかないケースがあった。
前提
# app/models/estimate.rb
class Estimate < ActiveRecord::Base
has_many :estimate_items
accepts_nested_attributes_for :estimate_items
end
❌ ダメなコード
# rails console
$> est = Estimate.find(1)
$> puts est.estimate_items.first.name
=> "test1"
$> est.estimate_items.first.name = "hoge"
$> est.save!
$> puts est.estimate_items.first.name
=> "test1" # 「hoge」が保存されてない!
🙆♂️ OKなコード
# rails console
$> est = Estimate.find(1)
$> puts est.estimate_items.first.name
=> "test1"
$> est.estimate_items[0].name = "hoge"
$> est.save!
$> puts est.estimate_items.first.name
=> "hoge" # 「hoge」が保存されている!
理由
firstがついているとSQLにlimitがついて has_many :estimate_items
の全量がロードされないから。
# rails console
est.estimate_items.first
#=> SELECT * from estimate_items where estimate_id = 1 limit 1
est.estimate_items[0]
#=> SELECT * from estimate_items where estimate_id = 1
ちなみに、うっかりデバッグでputsとかするとfirstでも保存されるようになる。
# rails console
$> est = Estimate.find(1)
$> puts est.estimate_items # ←デバッグ用のつもりが処理に影響を及ぼしている
#=> SELECT * from estimate_items where estimate_id = 1
$> puts est.estimate_items.first.name
=> "test1"
$> est.estimate_items.first.name = "hoge"
$> est.save!
$> puts est.estimate_items.first.name
=> "hoge" # 「hoge」が保存されている!
このロードの仕組みはデバッグがしづらいので結構ハマる。ちゃんと理解してないと本番環境でもRails.loggerが実際には処理に影響を及ぼすこともあるので要注意。
対策
accept_nested_attributesの時はfirst
使わず[0]
でデータ更新しよう。