Looks like this feature has been pulled from Edge Rails though it’s tentatively scheduled to come back post the 2.2 release.
Nested models (nested forms by another name) describe the scenario when you want to create and modify values of nested attributes through a containing model. For instance, if you have an user model with many phone numbers:
1 2 3 4 5 6 7 8 9 |
class User < ActiveRecord::Base validates_presence_of :login has_many :phone_numbers end class PhoneNumber < ActiveRecord::Base validates_presence_of :area_code, :number belongs_to :user end |
you may want to be able to create the user and a group of phone numbers at the same time. This is what this looks like with the new mass assignment functionality of Rails keyed off of the :accessible option of the association declaration (:phone_numbers, in this case).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class User < ActiveRecord::Base validates_presence_of :login has_many :phone_numbers, :accessible => true end ryan = User.create( { :login => 'ryan', :phone_numbers => [ { :area_code => '919', :number => '123-4567' }, { :area_code => '920', :number => '123-8901' } ] }) ryan.phone_numbers.count #=> 2 |
A single hash of values being sent to User.create results in both a new user object and new associated phone numbers. Previously, you would have had to manually create your own phone_numbers= setter method on user to get this same functionality:
1 2 3 4 5 6 7 8 9 10 11 |
class User < ActiveRecord::Base ... def phone_numbers=(attrs_array) attrs_array.each do |attrs| phone_numbers.create(attrs) end end end |
Mass assignment now gives you this functionality for free.
This may not look like much, but it is a step in the direction of letting you use nested forms. Consider a user registration form where a user can enter their login name and their phone numbers in the same form (through the use of fields_for which will bundle nested model attributes into a single form):
1 2 3 4 5 6 7 8 |
<% form_for @user do |f| %> <%= f.text_field :login %> <% fields_for :phone_numbers do |pn_f| %> <%= pn_f.text_field :area_code %> <%= pn_f.text_field :number %> <% end %> <%= submit_tag %> <% end %> |
This form, when submitted to the following standard RESTful UserController, will correctly create the user and its associated phone numbers through the beauty of mass assignment.
1 2 3 4 5 6 7 8 9 10 |
class UserController < ApplicationController # Create a new user and their phone numbers with mass assignment def new @user = User.create(params[:user]) respond_to do |wants| ... end end end |
Mass assignment can be used on all association types – :belongs_to, :has_one, :has_many and :has_and_belongs_to_many as long as the :accessible => true option is specified.
This is a very convenient addition to ActiveRecord, but the real zinger will come with full nested form support when you can create, update and delete these nested models directly from what is pushed down in the parameter hash of a form submission. This would allow for the functionality in this complex forms screencast with minimal hassle. What a fine day that would be.
tags: ruby, rubyonrails

Nice!
Though its worth mentioning that you can already do this with attribute_fu, even get javascript functionality for free for dynamic forms.
I can see this being useful also for constructing non-yml test fixtures.
In our app a donor can have multiple email addresses, phone numbers, addresses, web addresses, etc., so we built a whole infrastructure with getter and setter methods. Hooray for the person who was to brilliant to live with this inconvenience and, rather than muddling through, gave us accessible => true. Hip Hip!
2 words “hellz yes”
This is a useful tidbit, thanks for covering it and thanks to the person who committed this feature.
I’d like to understand the transaction scheme behind? Why is it using create rather than build? Anyone know? Thanks.
Please tell me that associated models validations will also bubble up to the parent or to some generic object where I can get at them… I’d love to be able to catch validations in associated models cleanly… This is awesome BTW, mass assignment recently saved me a few hundred lines, and this would save me a few hundred more…
Imagine I have: Client has_one address_a, class => Address, conditions => ‘a’ Client has_one address_b, class => Address, conditions => ‘b’ and class Address looks like belongs_to country belongs_to city belongs_to region ..... so the address model consists of references to other models. Will I be able to do smth like: ryan = User.create( { :login => ‘ryan’, :address_a => { :country => { :name => ‘Zimbabwe’ }, :region => { :name => ‘East side’ } } :address_b => { :country => { ............... } }) In other words: what’s the maximum level of nesting?
Wow! This seems to be a huge improvement! I had to create this kind of “setter” in many projects! Now it’ll be much easier!
Felipe Giotto.
I wrote a plugin and associated tests for something like this a few months ago. I wonder if this implementation is as robust with the edge cases. For example, can it handle the case where you mass assign some nested models, save, and then mass assign new nested models where some of them are the same as the original and some are not? In that case you would want to be sure that you do not delete the missing one’s until after validation has passed and that you are paying attention to ids.
How to use the same with daughter attr_accessors .
I have only one question?
Has anyone successfully used this new feature? I got the Edge Rails, and then tried this out, but it doesn’t seem to do anything.
the creation parameter names from the form are still not associated with the parent record. I.E. Parameters: {“user”=>{“login”=>”foobar”, “password”=>”barfoo”, “email”=>”foobar@foo.com”}, “commit”=>”Sign up”, “contact”=>{“first_name”=>”foo”}, “authenticity_token”=>”7f37bb0704c84b5a6f2ca3eb95c36f52501c3bfb”, “action”=>”create”, “controller”=>”users”}
Where I would assume the “contact” should be INSIDE the “user” parameter. My Contact record is polymorphic (contactable), but I don’t see why that would be a problem… or maybe that’s the only problem.
Anyways, if anyone else was successful, lemme know. Thanks
I have tried as well and this done not seem to be working. I get the same problem as Glenn Powell.. The child elements are not nested within the parent hash. If anyone has figured this out please let me know!
I got this to work by using
<% for phone_number in @user.phone_numbers %> <% fields_for “userphone_numbers”, phone_number do |pn_f| %> <%= pn_f.text_field :area_code %> <%= pn_f.text_field :number %> <% end %> <% end %>
but I had to build the phone numbers in the controller in the new action and cycle through them. I’m not really sure if this was necessary or if it should have worked a different way. But it did work!
Ok, I don’t know why I’ve never seen anyone use, mention, or document this (maybe it’s Edge only), but when making a nested form (ex. above), you can call fields_for FROM the parent FormBuilder object (the “f” variable). EX.
<% fields_for :user do |f| %> f.label … ... <% f.fields_for @user.contact do |f_contact| %> f_contact.text_field … ...
I would think that you could have used “f.fields_for :contact do |f_contact|” instead (just using the symbol :contact, but I keep getting this error when I try that:
@user[contact] is not allowed as an instance variable name
I would have preferred to use the :contact symbol only because then I wouldn’t have to explicitly initialize the ”@user.contact = Contact.new” in my user_controller.new method. But, if I do in fact follow this course of action, then the fields appear to be named correctly. (“usercontact” ...etc.)
In Edge rails, there is also an option that can be specified (options => { :index => index }) But this doesn’t seem to work for me. In face in order to get an array of nested children to have the correct field names, I have to use this:
<% @user.contact.emails.each do |email| %> <% f.fields_for “emails[]”, email do |f_email| %> ...
Go ahead and look into “rails/actionpack/lib/action_view/helpers/form_helper.rb” I found useful (and undocumented) features. But even though there does seem to be Array support in the FormBuilder.fields_for method, I still can’t find an elegant solution for my nested Array models.
Until I find more…
Finally, I got it working so that my controller doesn’t have to do ANYTHING other than: @user = User.new(params[:user]) and all the children are created correctly. You just must make sure the children are indicated in the “attr_accessible”. But only if that method call is used within your parent model, otherwise their assumed accessible.
Here’s a couple helper functions I wrote to make creating these forms much easier:
[code] def add_accessible_link(record_class, options, fields_options) record = record_class.new name = options[:name] ||= ActionController::RecordIdentifier.singular_class_name(record) update = options[:update] ||= name.pluralize partial = options[:partial] ||= name label = options[:label] ||= “Add #{name}” [/code]
add_accessible_link – This is to insert an “Add Model” link to your parent model which adds a new nested model child. The first param is the Record class of your child model (I.E. Email). The options param is to include:- :name => name of the local variable in the partial
- :update => id of html element to insert new partial at end of
- :partial => path to partial (I.E. ‘emails/edit’)
- :label => link’s text label
The fields_options param is simply passed into the partial, and is supposed to be then passed into the function described below as options param.
fields_for_accessible – This is used in your child partials in place of fields_for (or to wrap it at least) The record param is the actual record being used. This must be a record and not a Symbol, only because I can’t get it to work with Symbols. (See above comment) The options params are:Here’s a compound example: “views/users/new.html.erb”: [code] <% form_for :user, :url => users_path do |f| -%> <%= f.error_messages %> ... <%= render :partial => ‘contacts/edit’, :locals => { :contact => @user.contact, :fields_options => { :f => f } } %> [/code]
“views/contacts/_edit.html.erb”: [code] <% fields_for_accessible(contact, fields_options ||= {}) do |f| %> ... <%= render :partial => ‘emails/edit’, :collection => (contact ? contact.emails : {}), :as => :email, :locals => { :fields_options => { :f => f, :name => “emails[]” } } %> <%= add_accessible_link Email, { :label => “Add Email”, :partial => ‘emails/edit’ }, { :f => f, :name => “emails[]” } %> [/code]
“views/emails/_edit.html.erb”: [code] <% fields_for_accessible(email, fields_options ||= {}) do |f| %> ... [/code]
Hope that’s easy enough, and that it helps. Lemme know.
My last post messed the code up a bit, but I think you can still parse it out. :)
Can we use nested Fixtures now? (I didn’t see any modifications to fixtures.rb, so I’m assuming not.)
This is an amazing feature.
Thank you so much!
Could we get a pastie of the above code?..
Sure: http://pastie.org/244039
I have included as much code as I think is necessary. Let me know if explanations are in order.
Sure: http://pastie.org/244039
I have included as much code as I think is necessary. Let me know if explanations are in order.
I got this to work with simple (text) fields, but I’m having a LOT of trouble getting the same functionality with a file_field. Anyone have any suggestions?
Specifically, when I create the model with files (using attachment_fu to handle images), everything is fine.
If I edit a record without images, everything works fine.
If I try to edit a record with images, my app blows up on save. Checking the DB it is overwriting all of the info in my images table with NULL.
I have the same question as Alex… does this also solve nested validation issues?
I was frozen to edge rails from about 3 weeks ago, and with this:
belongs_to :address, :accessible => :true
I kept on getting
“Unknown key(s): accessible”
I just upgraded to rails 2.1, and I’m still getting the same error. Am I using the key incorrectly or is this functionality not in 2.1 that isn’t frozen to edge?
@Jennifer Maas
You need to check out Edge Rails from Github, freezing it with rake grabs an out of date version.
git clone git://github.com/rails/rails.git vendor/rails
For anyone trying this, the patch is temporarily taken out of edge. Discussion here: http://groups.google.com/group/rubyonrails-core/browse_thread/thread/3c61e00916c365e5
“attrs_array.each { |attrs| phone_numbers.create(attrs) }”
Ew!
When is Rails going to natively support single-call mass inserts, a la ActiveRecord::Extensions?