【问题标题】:Rails ActiveRecord associations inconsistently updatedRails ActiveRecord 关联更新不一致
【发布时间】:2010-02-12 23:48:23
【问题描述】:

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

最好用一个例子来解释:

创建具有字符串属性'title'Post 模型和具有字符串属性'content'Comment 模型。

以下是关联:

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

场景#1:在下面的代码中,我创建了一个Post 和一个关联的Comment,通过Post 创建第二个Post,将第一个Comment 添加到第一个@987654331 @ 并发现第二个 Post 与第二个 Comment 相关联,但没有明确分配。

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之前comment2添加到post1

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 中则有两个。

最大的问题:为什么在将新的 Comment 添加到 post1 之前运行 post2.comments 会对与 post2 关联的评论产生任何影响?

【问题讨论】:

    标签: ruby-on-rails activerecord associations has-many belongs-to


    【解决方案1】:

    这与 Active Record 缓存请求的方式以及处理 has_many 关联的方式有关。

    除非在查找过程中使用 :include 选项预先加载关联。在需要之前,Rails 不会填充找到的记录的关联。当需要关联时,会执行一些memoization 以减少执行的 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]
    

    注意post2.comment_ids 内部定义为post2.comments.map(&amp;:id)

    附:我对this question 的回答可能会帮助您理解为什么 post2 会在您没有触碰它的情况下得到更新。

    【讨论】:

    • 感谢您的回答。但这种行为似乎是错误的吗?缓存应该提高性能,而不是导致结果不一致。
    • 这更像是一个并发问题。 Rails 不希望外部源更改与模型实例相关的内容。在这种情况下,外部来源是指源自特定实例以外的某些地方的任何动作。如果坚信这是错误的,请提交错误报告。
    猜你喜欢
    • 1970-01-01
    • 2017-09-30
    • 2023-03-16
    • 2012-10-01
    • 2011-12-15
    • 1970-01-01
    • 1970-01-01
    • 2010-10-06
    相关资源
    最近更新 更多