How can you validate the presence of a belongs to association with Rails?

前端 未结 6 1191
夕颜
夕颜 2020-12-28 17:05

Say I have a basic Rails app with a basic one-to-many relationship where each comment belongs to an article:

$ rails blog
$ cd blog
$ script/generate model a         


        
相关标签:
6条回答
  • 2020-12-28 17:45

    If you're using Rails 2.3 you are using the new nested model stuff. I have noticed the same failures with validates_presence_of as you pointed out, or if :null => false was specified in the migration for that field.

    If your intent is to create nested models, you should add accepts_nested_attributes_for :comments to article.rb. This would allow you to:

    a = Article.new
    a.comments_attributes = [{:body => "test"}]
    a.save!  # creates a new Article and a new Comment with a body of "test"
    

    What you have is the way it should work to me, but I'm seeing that it doesn't with Rails 2.3.2. I debugged a before_create in comment using your code and no article_id is supplied through the build (it is nil) so this is not going to work. You'd need to save the Article first as Nicholas pointed out, or remove validation.

    0 讨论(0)
  • 2020-12-28 17:46

    This fails because there is no identity map in Rails 2.3 or 3.0. You can manually fix it by stitching them together.

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

    This is horrible, and what the identity map in 3.1 will help to fix (in addition to performance gains). c.article and a.comments.first.article are different objects without an identity map.

    0 讨论(0)
  • 2020-12-28 17:56

    I've also been investigating this topic and here is my summary:

    The root cause why this doesn't work OOTB (at least when using validates_presence_of :article and not validates_presence_of :article_id) is the fact that rails doesn't use an identity map internally and therefore will not by itself know that article.comments[x].article == article

    I have found three workarounds to make it work with a little effort:

    1. Save the article before creating the comments (rails will automatically pass the article id that was generated during the save to each newly created comments; see Nicholas Hubbard's response)
    2. Explicitly set the article on the comment after creating it (see W. Andrew Loe III's response)
    3. Use inverse_of:
      class Article < ActiveRecord::Base
        has_many :comments, :inverse_of => :article
      end

    This last solution was bot yet mentioned in this article but seems to be rails' quick fix solution for the lack of an identity map. It also looks the least intrusive one of the three to me.

    0 讨论(0)
  • 2020-12-28 18:03

    You are correct. The article needs an id before this validation will work. One way around this is the save the article, like so:

    >> 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
    

    If you are creating a new article with a comment in one method or action then I would recommend creating the article and saving it, then creating the comment, but wrapping the entire thing inside of a Article.transaction block so that you don't end up with any extra articles.

    0 讨论(0)
  • 2020-12-28 18:06

    I fixed this problem adding this follow line to my _comment.html.erb:

    "NEW" if form.object.new_record? %>

    Now, the validation works in stand alone form, and in multi form too.

    0 讨论(0)
  • 2020-12-28 18:09

    Instead of validating the presence of the article's id you could validate the presence of the article.

    validates_presence_of :article
    

    Then when you are creating your comment:

    article.comments.build :article => article
    
    0 讨论(0)
提交回复
热议问题