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

pjb*_*jb3 18 ruby ruby-on-rails

假设我有一个基本的Rails应用程序,其基本的一对多关系,其中每个评论都属于一篇文章:

$ rails blog
$ cd blog
$ script/generate model article name:string
$ script/generate model comment article:belongs_to body:text
Run Code Online (Sandbox Code Playgroud)

现在我添加代码来创建关联,但我也想确保在创建注释时,它总是有一篇文章:

class Article < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :article
  validates_presence_of :article_id
end
Run Code Online (Sandbox Code Playgroud)

现在让我们说我想创建一篇带有评论的文章:

$ rake db:migrate
$ script/console
Run Code Online (Sandbox Code Playgroud)

如果你这样做:

>> 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!
Run Code Online (Sandbox Code Playgroud)

你会收到这个错误:

ActiveRecord::RecordInvalid: Validation failed: Comments is invalid
Run Code Online (Sandbox Code Playgroud)

这是有道理的,因为评论还没有page_id.

>> article.comments.first.errors.on(:article_id)
=> "can't be blank"
Run Code Online (Sandbox Code Playgroud)

因此,如果我删除了validates_presence_of :article_idfrom comment.rb,那么我可以执行保存,但这也可以让您创建没有文章ID的注释.处理此问题的典型方法是什么?

更新:根据尼古拉斯的建议,这里有一个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
Run Code Online (Sandbox Code Playgroud)

我不确定是否要为每个一对多关联添加这样的内容.Andy可能是正确的,因为最好避免尝试进行级联保存并使用嵌套属性解决方案.我会暂时搁置一下,看看是否有人有任何其他建议.

Nie*_*eus 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

最后一个解决方案是本文中提到的bot,但似乎是rails的快速修复解决方案,因为缺少身份映射.它对我来说也是三个中最不具侵入性的一个.

  • Inverse_of完美地回答了这个问题.与其他解决方案不同,它可以很好地与其他Rails成语如accepts_nested_attributes_for一起使用. (6认同)

小智 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
Run Code Online (Sandbox Code Playgroud)

如果您要在一种方法或操作中创建带有评论的新文章,那么我建议创建该文章并保存它,然后创建评论,但将整个内容包装在 Article.transaction 块中,这样您就不会结束补充任何额外的文章。