如何验证属于Rails的属性的存在?

时间:2009-06-22 02:32:48

标签: ruby-on-rails ruby

假设我有一个基本的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可能是正确的,因为最好避免尝试进行级联保存并使用嵌套属性解决方案。我会暂时搁置一下,看看是否有人有任何其他建议。

6 个答案:

答案 0 :(得分:22)

我也在调查这个话题,这是我的总结:

为什么这不起作用的根本原因OOTB(至少在使用validates_presence_of :article而不是validates_presence_of :article_id时)是rails在内部不使用身份映射的事实因此本身不会知道article.comments[x].article == article

我找到了三种解决方法,只需付出一点努力就可以了:

  1. 在创建评论之前保存文章(rails会自动将保存期间生成的文章ID传递给每个新创建的评论;请参阅Nicholas Hubbard的回复)
  2. 创建评论后明确设置评论文章(参见W. Andrew Loe III的回复)
  3. 使用inverse_of:
    class Article < ActiveRecord::Base
      has_many :comments, :inverse_of => :article
    end
  4. 这个最后的解决方案是本文中提到的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.articlea.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(它是零)所以这不起作用。你需要先按照尼古拉斯的指示保存文章,或者删除验证。