views:

146

answers:

1

Thanks to http://stackoverflow.com/questions/2020673 and "Agile Web Dev", I know how to have multiple models in a form using fields_for. But I'm tearing my hair out over this one.

Suppose I have a model Person. Person has a name attribute, and has_many :foos. The Foo model, in turn, has a colour attribute.

Furthermore, I know that each Person has precisely three Foos. What should my Models, the new and create actions in PersonController, and the new view look like in order to present three nicely-labelled text-entry boxes, one for each Foo and capable of reporting validation errors, to allow my "new person" form to create the whole set of four objects in one go?

Also, can I do this without accepts_nested_attributes_for?

+1  A: 

After some playing about with varied locations for square braces and different for loops, I think I've solved this. Here's what my code looks like now (with routes set up as per scaffolding, so that posting from /new triggers create).

models/person.rb

class Person < ActiveRecord::Base
  has_many :foos
  validates_presence_of :name
end

models/foo.rb

class Foo < ActiveRecord::Base
  belongs_to :person
  validates_presence_of :colour
  validates_uniqueness_of :colour, :scope => "person_id"
end

controllers/people_controller.rb

def new
  # Set up a Person with 3 defaulted Foos
  @person = Person.new
  (1..3).each { |i| @person.foos.build }
end

def create
  # Create (but don't save) a Person as specified
  @person = Person.new(params[:person])

  # Create (but don't save) a Foo for each set of Foo details
  @foos = []
  params[:foo].each do |foo_param|
    @foos << Foo.new(foo_param)
  end

  # Save everything in a transaction
  Person.transaction do
    @person.save!
    @foos.each do |foo|
      foo.person = @person
      foo.save!
    end
  end

  redirect_to :action => 'show', :id => @person

rescue ActiveRecord::RecordInvalid => e
  @foos.each do |foo|
    foo.valid?
  end
  render :action => 'new'
end

views/people/new.html.erb

<% form_for :person do |f| %>
  <%= error_messages_for :object => [@person] + @person.foos %>

  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>

  <table>
  <% @person.foos.each_with_index do |foo, index| @foo = foo%>
    <tr>
      <td><%= label :colour, "Foo colour #{index + 1}: " %></td>
      <td><%= text_field("foo[]", "colour" %></td>
    </tr>          
  <% end %>
  </table>

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

This seems to do the trick.

Chris
Hmm. the uniqueness validation isn't quite working here. I'll try to figure that out.
Chris
No, I take it back. It was a bizarre interaction with my redirect and flash handling. I believe the code above works.
Chris