1

我遇到了一些我不理解的 Rails 2.3.5 ActiveRecord 行为。似乎一个对象可以以不一致的方式更新其关联 ID。

最好用一个例子来解释:

创建一个Post带有字符串属性的模型'title'和一个Comment带有字符串属性的模型'content'

以下是关联:

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

场景#1:在下面的代码中,我创建Post了一个关联的,通过'ing 第一个Comment创建第二个,将第二个添加到第一个并发现第二个与第二个关联而没有显式分配。PostfindCommentPostPostComment

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:再次运行上面的命令,但是这次插入一个额外的命令,从表面上看,应该不会影响结果。额外的命令post2.comments创建之后comment2和添加到.comment2post1

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]

请注意,在此场景中只有一个与相关联的评论,post2而在场景 1 中则有两个。

大问题:为什么要post2.comments在添加新的之前运行Commentpost1对与评论相关联的评论产生任何影响post2

4

1 回答 1

2

这与 Active Record 缓存请求的方式以及处理 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]

场景二:

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 会更新,尽管您没有触摸它。

于 2010-02-13T02:16:14.127 回答