Nested attributes - not working in forms

Hi! I also posted this on railsforum but then I realized this is probably not the best place to get answers for this problem. So, sorry for this crosspost… I also want to apologize for this long message. I just wanted to provide all information - on the one hand to make the error reproducable, on the other hand to enable people who maybe have the same problem in the future to get step-by-step instructions. I try to generate a form with an 1:many-model. Doing so, I found the command accepts_nested_attributes_for. However, in my form only the parent-model gets saved, while the child-model stays empty. I started by creating a project (I called it PhoneBook) in Rails 4. Then I generated two models and started the migration:

$ rails g model person name invoke active_record create db/migrate/20130710205309_create_people.rb create app/models/person.rb invoke test_unit create test/models/person_test.rb create test/fixtures/people.yml $ rails g model phone person_id:integer phone_number:integer invoke active_record create db/migrate/20130710205415_create_phones.rb create app/models/phone.rb invoke test_unit create test/models/phone_test.rb create test/fixtures/phones.yml $ rake db:migrate == CreatePeople: migrating =================================================== – create_table(:people) → 0.0008s == CreatePeople: migrated (0.0008s) ==========================================

== CreatePhones: migrating =================================================== – create_table(:phones) → 0.0007s == CreatePhones: migrated (0.0008s) ==========================================

Next, I specified the models

app/models/person.rb

class Person < ActiveRecord::Base

has_many :phones

accepts_nested_attributes_for :phones

end

app/models/phone.rb

class Phone < ActiveRecord::Base

belongs_to :person

end

Then I created a controller for person

$ rails g controller people create app/controllers/people_controller.rb invoke erb create app/views/people invoke test_unit create test/controllers/people_controller_test.rb invoke helper create app/helpers/people_helper.rb invoke test_unit create test/helpers/people_helper_test.rb invoke assets invoke coffee create app/assets/javascripts/people.js.coffee invoke scss create app/assets/stylesheets/people.css.scss

This controller was filled with the following code

class PeopleController < ApplicationController def new @person = Person.new @person.phones.new end

def create @person = Person.new(person_params) @person.save

redirect_to people_path

end

def index @person = Person.all end

private

def person_params params.require(:person).permit(:name, phone_attributes: [ :id, :phone_number ]) end

end

Of course I also needed some views. This one just shows the list of all people with their phone numbers

Phone Book

    <% @person.each do |person| %>

  • <%= person.name %>

      <ul>
        <% person.phones.each do |phone| %>
          <li>
            <%= phone.phone_number %>
          </li>
        <% end %>
      </ul>
    
    </li>
    

    <% end %>

and this one is the interesting part: here’s my code for the form that should create new people and phone numbers:

<%= form_for :person, url: people_path do |f| %>

<%= f.label :name %>
<%= f.text_field :name %>

<%= f.fields_for :phones do |f_phone| %>

<%= f_phone.label :phone_number %>
<%= f_phone.text_field :phone_number %>

<% end %>

<%= f.submit %>

<% end %>

To make it work (well it doesn’t work, but almost) I also need a route of course:

PhoneBook::Application.routes.draw do resources :people end

Are you still with me? Great! Creating a new person with a phone number in the rails console works without any problems. However, when I try my form, only the Person gets stored, while the phone number is lost.

Can you help me with this problem? What am I doing wrong?

Do I maybe need a controller for phone_number? As it is only accessed via person, I thought I wouldn’t need one.

Thank you in advance!

The fields_for form helper can only loop over association phone numbers that exist. The way to have new phone numbers added is to build them in advance. E.g.

@person.phones.build

Or if you want to supply 4 phone number fields:

4.times {@person.phone.build}

In that case you should add a reject_if option to your accept_nested_attributes :phone statement to have empty left phone numbers skipped.

You can build the phone numbers in you controller or use a form_helper like setup_person(person) in your forms.

See this Rails Cast: #196 Nested Model Form Part 1 - RailsCasts

Thank you, especially for linking the Railcast!

Watching this railcast helped me solving tis problem. However, .build is just an alias for .new, which I have already in def new.

The problem was about this line in the view:

<%= form_for :person, url: people_path do |f| %>

It should be

<%= form_for @person, url: people_path do |f| %>

instead.

Something else: a dedicated form object would be a better practise, right? Is there a detailled instruction on how to make a form object? Or a tutorial on adding classes not connected to ActiveRecords in general?

Thank you!