Doubly-nested models

Hi all,

I have three models, like so:

User
belongs_to :person
accepts_nested_attributes_for :person, :allow_destroy => false
has_many :contacts

Person
has_many :contacts
accepts_nested_attributes_for :contacts, :allow_destroy => true

Contact
belongs_to :person

And actually, it's a bit more complicated because Contact uses single-
table polymorphism and may be a PhoneNumber or an Address. Different
information would be filled in for each on the form, so right now I
have separate partials for each of those.

My goal is to create both the User and the Person, as well as a
Contact (if specified), when the first form is submitted. I've got it
so it creates the User and the Person, but it doesn't set any of the
data in the Person row. It won't create any Contacts at all.

# new.html.erb
<h1>Sign up</h1>

<% form_for @user, :url => account_path do |f| %>
  <%= f.error_messages %>
  <%= render :partial => "form", :object => f %>
  <p>
    <%= f.submit "Register" %>
  </p>
<% end %>

# _form.html.erb
<%= error_messages_for :user %>
<div class="user">
  <% form_for @user do |user_form| -%>

    <%= user_form.label :email %><br />
    <%= user_form.text_field :email %><br />
    <br />
    <%= user_form.label :password, user_form.object.new_record? ?
nil : "Change password" %><br />
    <%= user_form.password_field :password %><br />
    <br />
    <%= user_form.label :password_confirmation, 'Confirm' %><br />
    <%= user_form.password_field :password_confirmation %><br />
    <br />
    <%= render :partial => "person", :object => @person %>
  <% end -%>
</div>
<br />

# _person.html.erb
<div class="person">
  <% form_for @person do |person_form| -%>

    <%= person_form.label :title %><br />
    <%= person_form.text_field :title %><br />
    <br />
    <%= person_form.label :first_name %><br />
    <%= person_form.text_field :first_name %><br />
    <br />
    <%= person_form.label :last_name %><br />
    <%= person_form.text_field :last_name %><br />
    <br />
    <%= person_form.label :job_title %><br />
    <%= person_form.text_field :job_title %><br />
    <br />
    <div id="contacts">
      <%= render :partial => "address", :collection =>
@person.addresses %>
      <%= render :partial => "phone_number", :collection =>
@person.phone_numbers %>
    </div>
    <p>
      <%= add_contact_link :address %>
      <%= add_contact_link :phone_number %>
    </p>
  <% end -%>
</div>

# _phone_number.html.erb (_address is very similar)
<div class="phone_number">
  <% new_or_existing = phone_number.new_record? ? 'new' : 'existing'
%>
  <% prefix = "person[#{new_or_existing}_contact_attributes][]" %>

  <% fields_for prefix, phone_number do |contact_form| -%>
    <p>
      <%= contact_form.hidden_field :type %>
      Area + Phone <%= contact_form.text_field :value %><br/>
      <%= remove_contact_link :phone_number, 'remove' %>
    </p>
  <% end -%>
</div>

# users_controller.rb
  def new
    @user = User.new
    @person = @user.build_person
    @addresses = @person.addresses.build
    @phone_numbers = @person.phone_numbers.build
  end

  # POST /users
  # POST /users.xml
  def create
    @user = User.new(params[:user])
    if @user.save
      flash[:notice] = 'User was successfully registered.'
      redirect_back_or_default account_url
    else
      render :action => :new
    end
  end

Any thoughts? Apologies if things are a little unorthodox. I've been
fiddling for hours.

Thanks,
John

Thanks all, think I fixed it, mostly.

UsersController:
  def create
    @user = User.new(params[:user])
    @person = @user.build_person(params[:user][:person])
    @person.contacts_attributes = params[:person][:contacts]
    if @user.save
      flash[:notice] = 'User was successfully registered.'
      redirect_back_or_default account_url
    else
      render :action => :new
    end
  end

  def new
    @user = User.new
    @person = @user.build_person
    @person.addresses.build
    @person.phone_numbers.build
  end

new.html.erb:
<h1>Sign up</h1>

<% form_for @user, :url => account_path do |f| %>
  <%= f.error_messages %>
  <%= render :partial => "form", :object => f %>
  <p>
    <%= f.submit "Register" %>
  </p>
<% end %>

_form.html.erb
<%= error_messages_for :user %>
<div class="user">
  <% form_for @user do |user_form| -%>

    <%= user_form.label :email %>
    <%= user_form.text_field :email %>
    <br />
    <%= user_form.label :password, user_form.object.new_record? ?
nil : "Change password" %>
    <%= user_form.password_field :password %>
    <br />
    <%= user_form.label :password_confirmation, 'Confirm' %>
    <%= user_form.password_field :password_confirmation %>
    <br />
    <%= render :partial => "person", :object => @person %>
  <% end -%>
</div>
<br />

_person.html.erb:
<div class="person">
  <% fields_for @person do |person_form| -%>

    <%= person_form.label :title %><%= person_form.select :title,
Person::TITLES %>
    <%= person_form.label :first_name %><%=
person_form.text_field :first_name %>
    <%= person_form.label :last_name %><%=
person_form.text_field :last_name %><br />
    <br />
    <%= person_form.label :job_title %><br />
    <%= person_form.text_field :job_title %><br />
    <br />
    <div id="contacts">
      <%= render :partial => "address", :collection =>
@person.addresses %>
      <%= render :partial => "phone_number", :collection =>
@person.phone_numbers %>
    </div>
    <p>
      <%= add_contact_link :address %>
      <%= add_contact_link :phone_number %>
    </p>
  <% end -%>
</div>

_address.html.erb:
<div class="address">
  <% fields_for "person[contacts][]", address do |contact_form| -%>
    <p>
      Address<br/>
      <%= contact_form.hidden_field :type %><br/>
      <%= contact_form.select :label, Address::LABELS, :selected =>
Address::LABELS.first %>
      Street <%= contact_form.text_field :value %><br/>
      Zip+4 <%= contact_form.text_field :zip_plus_four, :limit => 10
%>
      <%= remove_contact_link :address, 'remove' %>
    </p>
  <% end -%>
</div>

And phone number is about the same as that.

The only problem is that it won't save the contents of the :type
hidden_field in the last partial. It fails to validate. That's a topic
for another post, though, I suppose.