【问题标题】:How can you validate the presence of a belongs to association with Rails?如何验证是否存在与 Rails 的关联?
【发布时间】:2012-01-18 06:06:51
【问题描述】:

假设我有一个基本的 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"

因此,如果我从 comment.rb 中删除 validates_presence_of :article_id,那么我可以进行保存,但这也将允许您创建没有文章 ID 的 cmets。典型的处理方式是什么?

更新:根据 Nicholas 的建议,这里有一个 save_with_cmets 的实现,它可以工作但很丑:

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

【问题讨论】:

    标签: ruby-on-rails ruby


    【解决方案1】:

    我也一直在研究这个话题,这是我的总结:

    OOTB 不起作用的根本原因(至少在使用 validates_presence_of :article 而不是 validates_presence_of :article_id 时)是 Rails 在内部不使用身份映射,因此它自己不会知道 @987654323 @

    我找到了三种解决方法,只需一点点努力即可:

    1. 在创建 cmets 之前保存文章(rails 会自动将保存期间生成的文章 ID 传递给每个新创建的 cmets;请参阅 Nicholas Hubbard 的回复)
    2. 在创建评论后明确设置评论(参见 W. Andrew Loe III 的回复)
    3. 使用 inverse_of:
      class Article < ActiveRecord::Base
        has_many :comments, :inverse_of => :article
      end

    最后一个解决方案是本文中提到的 bot,但似乎是 rails 针对缺少身份映射的快速修复解决方案。在我看来,它也是三者中干扰最小的一个。

    【讨论】:

    • Inverse_of 完美地回答了这个问题。与其他解决方案不同,它可以很好地与其他 Rails 惯用语(如 accept_nested_attributes_for)配合使用。
    • 这对我来说非常有效,使用具有嵌套属性的深度嵌套表单。通过首先保存父模型,在 parent_id 上创建具有验证的子对象意味着我可以保持验证:)
    • 设置 inverse_of 也对我有用,但我必须将存在验证从 _id 更改为仅 才能使其正常工作。
    • 是的.. 这是:inverse_of 的答案,即使它尚未保存也需要存在关联
    【解决方案2】:

    您可以验证文章的存在,而不是验证文章 id 的存在。

    validates_presence_of :article
    

    那么当你创建你的评论时:

    article.comments.build :article => article
    

    【讨论】:

    • validates_presence_of :article 和 validates_presence_of :article_id 有区别吗?在我看来,两者都还不错(Rails 3)。
    • 一篇尚未保存的文章可能仍然存在,但在整个保存事务完成之前缺少 id。
    【解决方案3】:

    你是对的。在此验证生效之前,文章需要一个 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 块中,这样您就不会'不要以任何额外的文章结束。

    【讨论】:

    • 因为我只能评论我自己的答案。 Daniel 是正确的,您可以使用 validates present of article 而不是 article_id,但是如果您使用的是 article.cmets.build,由于 rails 使用的构建器,这仍然无法正常工作。
    【解决方案4】:

    我解决了这个问题,将此后续行添加到我的 _comment.html.erb:

    “新”如果 form.object.new_record? %>

    现在,验证以独立形式工作,也以多种形式工作。

    【讨论】:

      【解决方案5】:

      这失败了,因为 Rails 2.3 或 3.0 中没有身份映射。您可以通过将它们缝合在一起来手动修复它。

      a = Article.new
      c = a.comments.build
      c.article = a
      a.save!
      

      这太可怕了,3.1 中的身份映射将有助于修复(除了性能提升之外)。 c.articlea.comments.first.article 是没有身份映射的不同对象。

      【讨论】:

        【解决方案6】:

        如果您使用的是 Rails 2.3,那么您使用的是新的嵌套模型。我注意到validates_presence_of 的失败与您指出的相同,或者如果在该字段的迁移中指定了:null =&gt; 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(它为nil),所以这不起作用。正如 Nicholas 指出的那样,您需要先保存文章,或者删除验证。

        【讨论】:

        • 是的,我认为问题是还没有文章ID,因为文章已经保存,因为它在验证期间运行。我相信操作的顺序是: 1. 验证文章 2. 验证所有 cmets 3. 保存文章 4. 保存 cmets 所以第 2 步失败,因为还没有 id。
        猜你喜欢
        • 1970-01-01
        • 2011-02-21
        • 2019-11-19
        • 2011-08-07
        • 2023-03-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多