rails association:autosave似乎没有按预期工作

时间:2015-12-24 15:56:09

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

我制作了一个真正基本的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

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

请注意,如果您不使用&#34; find_or_initialize&#34;,它可以作为ActiveRecord尊重关联缓存 - 否则它会过于频繁地重新加载注释,从而丢失更改。即,这个实现工作

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

但是,当然,如果我可以使用数据库,我不想在内存中浏览集合。此外,它似乎不一致,它适用于新对象,但不适用于现有对象。

3 个答案:

答案 0 :(得分:3)

这是另一种选择。如果它不是新记录,您可以将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

答案 1 :(得分:1)

我认为你无法做到这一点。当你使用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

答案 2 :(得分:0)

约翰Naegle是对的。但是你仍然可以在不使用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