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.