rails associations :autosave 似乎没有按预期工作
rails associations :autosave doesn't seem to working as expected
我做了一个真正的基础 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 尊重关联缓存 - 否则它会过于频繁地重新加载注释,从而丢弃更改。即,此实现有效
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
但是,当然,如果我可以用数据库完成的话,我不想遍历内存中的集合。另外,它适用于新对象而不是现有对象似乎不一致。
我认为你无法完成这项工作。当您使用 find_or_initialize_by 时,似乎没有使用 collection - 只是作用域。所以你得到了不同的 object.
如果你改变你的方法:
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 的评论不在 collection 中,而是在 collection 之外。如果你想让它起作用,我认为你需要像问题中那样使用检测和构建:
def comment=(val)
obj = comments.detect {|c| c.posted_at == Date.today } || comments.build(:posted_at => Date.today)
obj.text = val
end
John 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
这是另一种选择。如果 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
我做了一个真正的基础 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 尊重关联缓存 - 否则它会过于频繁地重新加载注释,从而丢弃更改。即,此实现有效
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
但是,当然,如果我可以用数据库完成的话,我不想遍历内存中的集合。另外,它适用于新对象而不是现有对象似乎不一致。
我认为你无法完成这项工作。当您使用 find_or_initialize_by 时,似乎没有使用 collection - 只是作用域。所以你得到了不同的 object.
如果你改变你的方法:
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 的评论不在 collection 中,而是在 collection 之外。如果你想让它起作用,我认为你需要像问题中那样使用检测和构建:
def comment=(val)
obj = comments.detect {|c| c.posted_at == Date.today } || comments.build(:posted_at => Date.today)
obj.text = val
end
John 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
这是另一种选择。如果 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