22

I've upgraded to Rails 2.3.3 (from 2.1.x) and I'm trying to figure out the accepts_nested_attributes_for method. I can use the method to update existing nested objects, but I can't use it to create new nested objects. Given the contrived example:

class Product < ActiveRecord::Base
  has_many :notes
  accepts_nested_attributes_for :notes
end

class Note < ActiveRecord::Base
  belongs_to :product
  validates_presence_of :product_id, :body
end

If I try to create a new Product, with a nested Note, as follows:

params = {:name => 'Test', :notes_attributes => {'0' => {'body' => 'Body'}}}
p = Product.new(params)
p.save!

It fails validations with the message:

ActiveRecord::RecordInvalid: Validation failed: Notes product can't be blank

I understand why this is happening -- it's because of the validates_presence_of :product_id on the Note class, and because at the time of saving the new record, the Product object doesn't have an id. However, I don't want to remove this validation; I think it would be incorrect to remove it.

I could also solve the problem by manually creating the Product first, and then adding the Note, but that defeats the simplicity of accepts_nested_attributes_for.

Is there a standard Rails way of creating nested objects on new records?

4 Answers 4

16

This is a common, circular dependency issue. There is an existing LightHouse ticket which is worth checking out.

I expect this to be much improved in Rails 3, but in the meantime you'll have to do a workaround. One solution is to set up a virtual attribute which you set when nesting to make the validation conditional.

class Note < ActiveRecord::Base
  belongs_to :product
  validates_presence_of :product_id, :unless => :nested
  attr_accessor :nested
end

And then you would set this attribute as a hidden field in your form.

<%= note_form.hidden_field :nested %>

That should be enough to have the nested attribute set when creating a note through the nested form. Untested.

Sign up to request clarification or add additional context in comments.

4 Comments

In Rails 3 this problem is solved by the added :inverse_of option to has_many, has_one and belongs_to. See e.g. daokaous.com/rails3.0.0_doc/classes/ActiveRecord/Associations/… under the section "Bi-directional associations"
I've opted to disable the validation when :id == nil. Since that should only happen when writing a new, nested record, I'm hoping that will be safe. Odd that this issue made it all the way to 2.3.8.
inverse_of doesn't seem to work when both the record's parent doesn't exist yet, and thus no id can be tested. Looks like this nested hack is the only way through. :(
I'm experiencing this issue in Rails 3, any updates on how to solve it?
7

check this document if you use Rails3.

http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#label-Validating+the+presence+of+a+parent+model

2 Comments

very important point is you should validate presence of "product" not "product_id"
I think kuboon has the right answer. Validate presence of "product", not "product_id" along with changing the association on Product to "has_many :notes, inverse_of: :product" should do it.
3

Ryan's solution is actually really cool. I went and made my controller fatter so that this nesting wouldn't have to appear in the view. Mostly because my view is sometimes json, so I want to be able to get away with as little as possible in there.

class Product < ActiveRecord::Base
  has_many :notes
  accepts_nested_attributes_for :note
end

class Note < ActiveRecord::Base
  belongs_to :product
  validates_presence_of :product_id  unless :nested
  attr_accessor :nested
end

class ProductController < ApplicationController

def create
    if params[:product][:note_attributes]
       params[:product][:note_attributes].each { |attribute| 
          attribute.merge!({:nested => true})
    }
    end
    # all the regular create stuff here  
end
end

Comments

0

Best solution yet is to use parental_control plugin: http://github.com/h-lame/parental_control

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.