Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create nested objects using accepts_nested_attributes_for

Tags:

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?

like image 438
peterjm Avatar asked Jul 30 '09 20:07

peterjm


People also ask

What is a nested attribute?

Nested attributes are a way of applying sub-categories to your attributes. For instance, instead of having a single searchable attribute price , you may set up some sub-categories: price.net , price. gross , price. margin (note the use of 'dot notation' here to separate the parent attribute from its child).

What is Accepts_nested_attributes_for?

accepts_nested_attributes_for(*attr_names) public. Defines an attributes writer for the specified association(s). Supported options: :allow_destroy. If true, destroys any members from the attributes hash with a _destroy key and a value that evaluates to true (eg.


3 Answers

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.

like image 61
ryanb Avatar answered Oct 04 '22 00:10

ryanb


check this document if you use Rails3.

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

like image 44
kuboon Avatar answered Oct 04 '22 00:10

kuboon


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
like image 33
Graeme Worthy Avatar answered Oct 03 '22 23:10

Graeme Worthy