[rails][rails3]ActiveRecordのassociationに関するメモ
【コード1】
class Parent < ActiveRecord::Base has_many :children end class Child < ActiveRecord::Base belongs_to :parent # その他、自分自身のvalidationコードを以下に記載・・・ validates :hoge, :presence => true validates :fuga, :presence => true end
としたときに、
【コード2】
require 'test_helper' class AuthorTest < ActiveSupport::TestCase test "author_names test 1" do prm = { :wible => "ccc", :boo => "ddd", :jeb => "eee" } parent = Parent.new(prm) assert parent.valid? ch = Child.new({ :hoge => "aaa", :fuga => "bbb" }) assert ch.valid? p "parent.id is " + parent.id.to_s # 当然、この時点ではIDはない p "ch.id is " + ch.id.to_s parent.children << ch p "parent.id is " + parent.id.to_s # この時点でもIDはない p "ch.id is " + ch.id.to_s assert parent.save p "parent.id is " + parent.id.to_s # IDができる(DBに保存される) p "ch.id is " + ch.id.to_s end end
これはまぁいい。
ところが
【コード3】
class Parent < ActiveRecord::Base has_many :children end class Child < ActiveRecord::Base belongs_to :parent #### 以下のvalidationを追加!! #### validates :parent_id, :presence => true #### 追加おわり!! #### # その他、自分自身のvalidationコードを以下に記載・・・ validates :hoge, :presence => true validates :fuga, :presence => true end
というふうにすると、さっきのテストの挙動が変わってくる。
【コード4】
equire 'test_helper' class AuthorTest < ActiveSupport::TestCase test "test parent-children" do prm = { :wible => "ccc", :boo => "ddd", :jeb => "eee" } parent = Parent.new(prm) assert parent.valid? ch = Child.new({ :hoge => "aaa", :fuga => "bbb" }) assert ch.valid? p "parent.id is " + parent.id.to_s # 当然、この時点ではIDはない p "ch.id is " + ch.id.to_s parent.children << ch p "parent.id is " + parent.id.to_s # この時点でもIDはない p "ch.id is " + ch.id.to_s assert parent.save #### ここでsaveできず、assertionにひっかかる!! #### ## 以下は実行されない ## p "parent.id is " + parent.id.to_s # IDができる(DBに保存される) p "ch.id is " + ch.id.to_s end end
associationでは、parent_idのvalidatioで"validates :parent_id, :presence => true"とはしないほうが良さそう。やりたい気持ちはすごくあるが、保存のタイミングによってこのvalidationをつけるかいなかが変わってくると思う。
個人的に思うに、まずparentが先に必ずDBに保存され、その後に追加でchildをparentに関連付けるという処理なら":presence => true"はつけてもOKだと思う。
でも、parentとchildを同時に保存する場合は":presence => true"はつけない方が良さそう(【コード1】)。
rails本だとこのような場合は、先にまずchildを確実に保存し(保存するときにsave!メソッドを使うと書いてあった)、次にparentを保存し、最後にparentとchildを関連付けるのが確実というようなことが書いてあった。ただ、この場合トランザクションにして、途中でエラーが発生したらロールバックできるようにしておかないとかな。
先にchildを保存するならどちらにしろ"validates :parent_id, :presence => true"の検証は付けられない。
結論としては、parentとchildを同時に保存することがある場合は、Childクラスに"validates :parent_id, :presence => true"の検証は付けないほうが無難だと思う。
追記
と思ったら違った。何が違ったかというと、"rails本だとこのような場合は、・・・"のくだり。あれはあくまで既に親レコードが存在し、その上で子を追加する場合の処理だ。
rails本によれば、親レコードを保存するときに関連する子レコードも一気に保存するが、これは自動でトランザクションにラップされているとのこと。つまり結局【コード2】がベストだと言うことだと思う。
つまりアルゴリズム的には
①子の検証→【エラーの場合】エラー処理
↓
②親の検証→【エラーの場合】エラー処理
↓
③子を親に代入
↓
④親を保存→【エラーの場合】トランザクションでラップされるのですべてのレコードは保存されない
という感じにするのがベストだろう。