假设我有一个基本的Rails应用程序,其基本的一对多关系,其中每个评论都属于一篇文章:
$ rails blog
$ cd blog
$ script/generate model article name:string
$ script/generate model comment article:belongs_to body:text
现在我添加代码来创建关联,但我也想确保在创建注释时,它总是有一篇文章:
class Article < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :article
validates_presence_of :article_id
end
现在让我们说我想一次创建一篇带有评论的文章:
$ rake db:migrate
$ script/console
如果你这样做:
>> article = Article.new
=> #<Article id: nil, name: nil, created_at: nil, updated_at: nil>
>> article.comments.build
=> #<Comment id: nil, article_id: nil, body: nil, created_at: nil, updated_at: nil>
>> article.save!
你会收到这个错误:
ActiveRecord::RecordInvalid: Validation failed: Comments is invalid
这是有道理的,因为评论还没有page_id。
>> article.comments.first.errors.on(:article_id)
=> "can't be blank"
因此,如果我从validates_presence_of :article_id
删除comment.rb
,那么我可以执行保存,但这也可以让您创建没有文章ID的注释。处理此问题的典型方法是什么?
更新:根据Nicholas的建议,这里有一个save_with_comments的实现,但是很难看:
def save_with_comments
save_with_comments!
rescue
false
end
def save_with_comments!
transaction do
comments = self.comments.dup
self.comments = []
save!
comments.each do |c|
c.article = self
c.save!
end
end
true
end
我不确定是否要为每个一对多关联添加这样的内容。 Andy可能是正确的,因为最好避免尝试进行级联保存并使用嵌套属性解决方案。我会暂时搁置一下,看看是否有人有任何其他建议。
答案 0 :(得分:22)
我也在调查这个话题,这是我的总结:
为什么这不起作用的根本原因OOTB(至少在使用validates_presence_of :article
而不是validates_presence_of :article_id
时)是rails在内部不使用身份映射的事实因此本身不会知道article.comments[x].article == article
我找到了三种解决方法,只需付出一点努力就可以了:
class Article < ActiveRecord::Base
has_many :comments, :inverse_of => :article
end
这个最后的解决方案是本文中提到的bot,但似乎是rails的快速修复解决方案,因为缺少身份映射。它对我来说也是三个中最不干扰的一个。
答案 1 :(得分:1)
你是对的。在此验证工作之前,该文章需要一个id。解决这个问题的一种方法是保存文章,如下所示:
>> article = Article.new
=> #<Article id: nil, name: nil, created_at: nil, updated_at: nil>
>> article.save!
=> true
>> article.comments.build
=> #<Comment id: nil, article_id: 2, body: nil, created_at: nil, updated_at: nil>
>> article.save!
=> true
如果您要在一个方法或操作中创建带有注释的新文章,那么我建议您创建文章并保存它,然后创建注释,但将整个内容包装在Article.transaction块中,以便您不要最终得到任何额外的文章。
答案 2 :(得分:1)
如果您可以验证文章的存在,则无需验证文章的存在。
validates_presence_of :article
然后,当您创建评论时:
article.comments.build :article => article
答案 3 :(得分:1)
我修复了此问题,将此后续行添加到我的_comment.html.erb:
如果form.object.new_record,“NEW”? %GT;现在,验证以独立形式运行,也以多种形式运行。
答案 4 :(得分:1)
由于Rails 2.3或3.0中没有身份映射,因此失败。您可以通过将它们拼接在一起来手动修复它。
a = Article.new
c = a.comments.build
c.article = a
a.save!
这太可怕了,3.1中的身份图有助于修复(除了性能提升)。 c.article
和a.comments.first.article
是没有身份地图的不同对象。
答案 5 :(得分:0)
如果您使用的是Rails 2.3,那么您正在使用新的嵌套模型。我已经注意到您指出的validates_presence_of
失败,或者在该字段的迁移中指定了:null => false
。
如果您的目的是创建嵌套模型,则应将accepts_nested_attributes_for :comments
添加到article.rb
。这将允许您:
a = Article.new
a.comments_attributes = [{:body => "test"}]
a.save! # creates a new Article and a new Comment with a body of "test"
你所拥有的是它应该对我有用的方式,但我发现它不适用于Rails 2.3.2。我使用您的代码在注释中调试了before_create
,并且没有通过构建提供article_id
(它是零)所以这不起作用。你需要先按照尼古拉斯的指示保存文章,或者删除验证。