1

理解できない Rails 2.3.5 ActiveRecord の動作に遭遇しました。オブジェクトのアソシエーション ID が一貫性のない方法で更新される可能性があるようです。

これは、次の例で最もよく説明されています。

Poststring 属性'title'を持つCommentモデルとstring 属性を持つモデルを作成します'content'

協会は次のとおりです。

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

シナリオ #1: 次のコードPostでは、関連付けられた を使用して 1 つを作成し、1 つCommentPostfind'ing して 2 つ目を作成し、1 つ目に 2 つ目Commentを追加して、明示的な割り当てなしで2 つ目が関連付けられていることPostを発見します。PostComment

post1 = Post.new
post1 = Post.new(:title => 'Post 1')
comment1 = Comment.new(:content => 'content 1')
post1.comments << comment1
post1.save
# Create a second Post object by find'ing the first
post2 = Post.find_by_title('Post 1')
# Add a new Comment to the first Post object
comment2 = Comment.new(:content => 'content 2')
post1.comments << comment2 
# Note that both Comments are associated with both Post objects even
# though I never explicitly associated it with post2.
post1.comment_ids # => [12, 13]
post2.comment_ids # => [12, 13]

シナリオ #2: 上記のコマンドを再度実行しますが、今回は結果に影響を与えない追加のコマンドを 1 つ挿入します。追加のコマンドは、作成、に追加する前にpost2.comments発生するものです。comment2comment2post1

post1 = Post.new
post1 = Post.new(:title => 'Post 1A')
comment1 = Comment.new(:content => 'content 1A')
post1.comments << comment1
post1.save
# Create a second Post object by find'ing the first
post2 = Post.find_by_title('Post 1A')
# Add a new Comment to the first Post object
comment2 = Comment.new(:content => 'content 2A')
post2.comments # !! THIS IS THE EXTRA COMMAND !!
post1.comments << comment2 
# Note that both Comments are associated with both Post objects even
# though I never explicitly associated it with post2.
post1.comment_ids # => [14, 15]
post2.comment_ids # => [14]

シナリオ 1 では 2 つのコメントがあったのに対し、このシナリオでは1 つのコメントしか関連付けられていないことに注意してください。post2

大きな疑問:新規をpost2.comments追加する前に実行して、どのコメントが に関連付けられているかに違いがあるのはなぜですか?Commentpost1post2

4

1 に答える 1

2

これは、ActiveRecordがリクエストをキャッシュする方法とhas_manyアソシエーションが処理される方法と関係があります。

検索中にアソシエーションが:includeオプションで熱心にロードされていない限り。Railsは、必要になるまで、見つかったレコードの関連付けを設定しません。関連付けが必要な場合は、実行されるSQLクエリの数を減らすためにメモ化が行われます。

質問のコードをステップスルーします。

post1 = Post.new(:title => 'Post 1')
comment1 = Comment.new(:content => 'content 1')
post1.comments << comment1  # updates post1's internal comments cache
post1.save 

# Create a second Post object by find'ing the first
post2 = Post.find_by_title('Post 1') 

# Add a new Comment to the first Post object
comment2 = Comment.new(:content => 'content 2')
post1.comments << comment2   # updates post1's internal comments cache

# Note that both Comments are associated with both Post objects even
# though I never explicitly associated it with post2.
post1.comment_ids # => [12, 13]

# this is the first time post2.comments are loaded. 
# SELECT comments.* FROM comments JOIN comments.post_id = posts.id WHERE posts.id = #{post2.id}
post2.comment_ids # => [12, 13]

シナリオ2:

post1 = Post.new(:title => 'Post 1A')
comment1 = Comment.new(:content => 'content 1A')
post1.comments << comment1
post1.save

# Create a second Post object by find'ing the first
post2 = Post.find_by_title('Post 1A')

# Add a new Comment to the first Post object
comment2 = Comment.new(:content => 'content 2A')

# first time post2.comments are loaded. 
# SELECT comments.* FROM comments JOIN comments.post_id = posts.id WHERE 
#   posts.id = post2.comments #=> Returns one comment (id = 14)
# cached internally.

post1.comments << comment2 
# Note that both Comments are associated with both Post objects even
# though I never explicitly associated it with post2.
post1.comment_ids # => [14, 15]

# post2.comment has already been cached, so the SQL query is not executed again.

post2.comment_ids # => [14]

NBpost2.comment_idsは内部的に次のように定義されていますpost2.comments.map(&:id)

PSこの質問に対する私の答えは、post2に触れていないのになぜpost2が更新されるのかを理解するのに役立つかもしれません。

于 2010-02-13T02:16:14.127 に答える