代码之家  ›  专栏  ›  技术社区  ›  pjb3

如何验证是否存在与Rails关联的belongs-to?

  •  18
  • pjb3  · 技术社区  · 16 年前

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

    这是有道理的,因为评论还没有页面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
    

    我不确定是否要为每个“一对多”关联添加这样的内容。安迪可能是正确的,因为最好避免尝试执行级联保存并使用嵌套属性解决方案。我暂时不谈这个,看有没有人有其他建议。

    6 回复  |  直到 16 年前
        1
  •  22
  •   Niek Bartholomeus    13 年前

    我也一直在调查这个话题,以下是我的总结:

    这不起作用的根本原因是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. 使用逆运算:
      class Article < ActiveRecord::Base
        has_many :comments, :inverse_of => :article
      end

    最后一个解决方案是本文中提到的bot,但似乎是rails的快速修复解决方案,因为缺少标识映射。在我看来,这也是三者中最不具侵入性的一个。

        2
  •  1
  •   Nicholas Hubbard    16 年前

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

        3
  •  1
  •   Daniel X Moore mcdon    16 年前

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

    validates_presence_of :article
    

    然后,当您创建评论时:

    article.comments.build :article => article
    
        4
  •  1
  •   Davis Z. Cabral    15 年前

    我修复了将以下行添加到我的@u comment.html.erb中的问题:

    如果form.object.NEW_记录为“NEW”?%>

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

        5
  •  1
  •   W. Andrew Loe III    14 年前

    这会失败,因为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 是没有身份映射的不同对象。

        6
  •  0
  •   Andrew Atkinson    16 年前

    如果您使用的是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"
    

    你所拥有的是它应该对我起作用的方式,但我发现它不适用于Rails2.3.2。我调试了一个 before_create 在注释中使用您的代码而不是 article_id 是通过构建提供的(它为零),所以这不起作用。你需要先保存文章,或者删除验证。