[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】がベストだと言うことだと思う。

つまりアルゴリズム的には

①子の検証→【エラーの場合】エラー処理

②親の検証→【エラーの場合】エラー処理

③子を親に代入

④親を保存→【エラーの場合】トランザクションでラップされるのですべてのレコードは保存されない

という感じにするのがベストだろう。