【问题标题】:rails associations :autosave doesn't seem to working as expectedrails associations :autosave 似乎没有按预期工作
【发布时间】:2016-03-31 00:56:30
【问题描述】:

我做了一个真正的基本 github 项目here 来演示这个问题。基本上,当我创建新评论时,它会按预期保存;当我更新现有评论时,它不会被保存。但是,:autosave => true 的文档并不是这样说的……他们说的恰恰相反。代码如下:

class Post < ActiveRecord::Base
  has_many :comments, 
           :autosave => true, 
           :inverse_of => :post,
           :dependent => :destroy

  def comment=(val)
    obj=comments.find_or_initialize_by(:posted_at=>Date.today)
    obj.text=val
  end
end

class Comment < ActiveRecord::Base
  belongs_to :post, :inverse_of=>:comments
end

现在在控制台中,我测试:

p=Post.create(:name=>'How to groom your unicorn')
p.comment="That's cool!"
p.save!
p.comments # returns value as expected. Now we try the update case ... 

p.comment="But how to you polish the rainbow?"
p.save!
p.comments # oops ... it wasn't updated

为什么不呢?我错过了什么?

请注意,如果您不使用“find_or_initialize”,它的工作原理是 ActiveRecord 尊重关联缓存 - 否则它会过于频繁地重新加载 cmets,从而丢弃更改。即,此实现有效

def comment=(val)
  obj=comments.detect {|obj| obj.posted_at==Date.today}
  obj = comments.build(:posted_at=>Date.today) if(obj.nil?)
  obj.text=val
end

当然,如果我可以对数据库进行操作,我不想遍历内存中的集合。另外,它与新对象而不是现有对象一起使用似乎不一致。

【问题讨论】:

  • 在您的第一个示例中,您是否尝试将最后一行称为 p.comment.reload
  • 不重要,因为无论你做什么,activerecord 都会决定重新加载整个集合。

标签: ruby-on-rails ruby ruby-on-rails-4 activerecord associations


【解决方案1】:

这是另一种选择。如果find_or_initialize_by返回的记录不是新记录,可以显式添加到集合中。

def comment=(val)
  obj=comments.find_or_initialize_by(:posted_at=>Date.today)
  unless obj.new_record?
    association(:comments).add_to_target(obj) 
  end
  obj.text=val
end

【讨论】:

  • 有趣。我以前不知道 Association() 选项。
【解决方案2】:

我认为您无法完成这项工作。当您使用 find_or_initialize_by 时,看起来好像没有使用该集合 - 只是范围。所以你得到了一个不同的对象。

如果你改变你的方法:

  def comment=(val)
    obj = comments.find_or_initialize_by(:posted_at => Date.today)
    obj.text = val
    puts "obj.object_id: #{obj.object_id} (#{obj.text})"
    puts "comments[0].object_id: #{comments[0].object_id} (#{comments[0].text})"
    obj.text
  end

你会看到这个:

p.comment="But how to you polish the rainbow?"
obj.object_id: 70287116773300 (But how to you polish the rainbow?)
comments[0].object_id: 70287100595240 (That's cool!)

所以来自 find_or_initialize_by 的评论不在集合中,它在集合之外。如果您希望它起作用,我认为您需要像问题中那样使用检测和构建:

  def comment=(val)
    obj = comments.detect {|c| c.posted_at == Date.today } || comments.build(:posted_at => Date.today)
    obj.text = val
  end

【讨论】:

  • 我认为你是对的。看起来我真正想要的是集合具有“部分加载”状态,这样当 Rails 尝试加载集合时,它会注意到内存中已经有一些对象,然后将这些对象与加载的对象合并从数据库中取出,而不是简单地将内存中的对象扔出去。
【解决方案3】:

约翰·内格尔是对的。但是您仍然可以在不使用detect 的情况下做您想做的事。由于您只更新今天的评论,您可以通过posted_date 订购关联,只需访问comments 集合的第一个成员即可更新它。 Rails 会从那里自动保存:

class Post < ActiveRecord::Base
  has_many :comments, ->{order "posted_at DESC"}, :autosave=>true,     :inverse_of=>:post,:dependent=>:destroy

  def comment=(val)
    if comments.empty? || comments[0].posted_at != Date.today
      comments.build(:posted_at=>Date.today, :text => val)
    else
      comments[0].text=val
    end
  end
end

【讨论】:

  • 如果 cmets.empty?如果你昨天发了,那么今天回来呢?看起来它更新了昨天的评论,而不是创建新记录。
  • 真的。我的错。您可以添加检查以查看第一条评论是否来自今天:if comments.empty? || comments[0].posted_at != Date.today
  • "cmets[0]" 行会导致整个集合被加载到内存中,因此它与检测的效果相同——所有对象都被加载——尽管你可以跳过输入——记忆搜索。所以它更好,但从性能的角度来看并没有好多少。而且我仍然必须编写条件“if”,这是我们试图避免使用“find_or_initialize_by”的一部分。不过,谢谢 - 这是一个聪明的主意。
猜你喜欢
  • 2020-09-15
  • 2016-01-24
  • 1970-01-01
  • 2022-12-17
  • 2021-06-07
  • 2013-02-10
  • 2019-04-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多