10

I am trying to reproduce railscast #196 in Rails 4. However, I'm experiencing some problems.

In my example I try to generate a Phonebook - each Person could have multiple PhoneNumbers

These are important parts of my controller:

class PeopleController < ApplicationController
    def new
        @person = Person.new
        3.times{ @person.phones.build }
    end

    def create
        @person = Person.create(person_params)
        @person.phones.build(params[:person][:phones])

        redirect_to people_path
    end

private

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

and this is my new view

<h1>New Person</h1>

<%= form_for :person, url: people_path do |f| %>
    <p>
        <%= f.label :name %> </ br>
        <%= f.text_field :name %>
    </p>

    <%= f.fields_for :phones do |f_num| %>
        <p>
            <%= f_num.label :number %> </ br>
            <%= f_num.text_field :number %>
        </p>
    <% end %>

    <p>
        <%= f.submit %>
    </p>
<% end %>

needless to say i have has_many :phones and accepts_nested_attributes_for :phones in the my person model and belongs_to :person in the phone model.

I have the following issues:

  1. Instead of 3 phone-number-fields there is just one in the new form
  2. When I submit the form I get an error:

ActiveModel::ForbiddenAttributesError

in the line

@person.phones.build(params[:person][:phones])

Parameters:

{"utf8"=>"✓",
 "authenticity_token"=>"l229r46mS3PCi2J1VqZ73ocMP+Ogi/yuYGUCMu7gmMw=",
 "person"=>{"name"=>"the_name",
 "phones"=>{"number"=>"12345"}},
 "commit"=>"Save Person"}

In principle I would like to do this whole thing as a form object, but I think if I don't even get it with accepts_nested_attributes, I have no chance to do it as a form object :(

speendo
  • 11,483
  • 19
  • 66
  • 100

1 Answers1

12

In order to get three phones in the view change form_for :person to form_for @person (you want to use the object you've built here) as follows:

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

This should fix the ForbiddenAttributes error as well.

And your create action could be:

def create
    @person = Person.create(person_params)

    redirect_to people_path
end

Update:

<%= form_for :person do |f| %> creates a generic form for the Person model and is not aware of the additional details you apply to a specific object (in this case @person in your new action). You've attached three phones to the @person object, and @person is not the same as :person which is why you didn't see three phone fields in your view. Please reference: http://apidock.com/rails/ActionView/Helpers/FormHelper/form_for for further details.

vee
  • 36,569
  • 5
  • 65
  • 72
  • yay! That solved both problems - can't say how glad I am! In the basic rails tutorial they use a hash: http://guides.rubyonrails.org/getting_started.html#the-first-form - can you elaborate the difference? – speendo Aug 30 '13 at 20:58
  • 1
    @speendo, Please see the update, if you meant the difference between using symbol vs. using object. – vee Aug 30 '13 at 21:17
  • I have a follow up comment. If the phone model has a phone_type_id, and `code` def new @person = Person.new ph = Person.phone.build ph[:phone_type_id] = 1 ph2 = Person.phone.build ph2[:phone_type_id] = 2 end `code` How would the view know the phone type id's and send it back without saving `code`@person`code` at the end of the new method? – tandoan Dec 02 '13 at 19:16
  • This works very nice, but there's something missing. When for some reason the inputs are invalid and `person` can't be created, following same guides @speendo said, we do `render 'new'` and fields of `phones`(or in my case `emails`) are gone. – unmultimedio Aug 08 '15 at 22:17
  • @unmultimedio, you also need to make sure you build any necessary associations before calling `render 'new'`. – vee Aug 08 '15 at 22:35