Solution for multiple models on one form with nested collections and nested objects.
I got things working and the short is that it doesn't seem that (1.1.6) Rails likes creating nested collections with nested objects in them from form values. While I love Rails, I don't see why this is an issue. But I suppose as long as there's a work around, I'm cool with that and hopefully this helps others that might run into this issue.
My form is a resume form where a user can have multiple education histories and each education history can have one address. The address partial is common both to the resume object as well as the nested education history objects.
I have, as one would expect, the form broken up into multiple partials, each one wrapping their specific model. So there's an _address partial for all address objects used on the form as well as a _education_histories partial, which renders the _address partial. Also I may note that the education histories are Ajax based, so I can add/remove a history w/out forcing a page reload.
Models:
class Resume < ActiveRecord::Base
has_one :address
has_and_belongs_to_many :education_histories
endclass EducationHistory < ActiveRecord::Base
has_and_belongs_to_many :resumes
has_one :address
def address_attr=(attributes)
build_address if address.nil?
address.attributes = attributes
end
end
class Address < ActiveRecord::Base
belongs_to :resume
belongs_to :education_history
end
Controller (which I'm only listing partially and including the ajax methods):
class ResumeController < ApplicationController
def new
@resume = Resume.new
@resume.education_histories << EducationHistory.new(:address=>Address.new)
@resume.address = Address.new
end def create
@resume = Resume.new(params[:resume])
params[:education_histories].each_value { |history| @resume.education_histories.build(history) }
if @resume.save
render :action => 'success'
else
render :action => 'resume'
end
end
def add_education_history # Ajax
@resume = Resume.new(params[:resume])
params[:education_histories].each_value { |history| @resume.education_histories.build(history) }
@resume.education_histories << EducationHistory.new(:address=>Address.new)
render :partial => 'education_history'
end
def remove_education_history # Ajax
@resume = Resume.new(params[:resume])
params[:education_histories].each_value { |history| @resume.education_histories.build(history) }
@resume.education_histories.pop
render :partial => 'education_history'
end
end
Views (w/relevant snippets):
resume.rhtml
<% form_for :resume, @resume, :url => { :action => "create" },
:html => {:name=>'resume_form',:id=>'resume_form'} do |@resume_form| %>
<p>Resume title:<br/><%= @resume_form.text_field :title %></p><p>Your address:<br/>
<% fields_for :address, @resume.address do |@address_fields| %>
<%=render :partial => 'address'%>
<%end%><p>
<div id="education_history"><%= render :partial=>"education_histories" %></div>
<% end -%>
_education_histories.rhtml
...
<% # Ajax add/remove education history links %>
<% if @resume.education_histories.length < 11 %><%= link_to_remote 'Add education history',
:update=>'education_history', :url => { :action =>'add_education_history' },
:with => "Form.serialize($('resume_form'))" %>
<% end %><% if @resume.education_histories.length > 1 %> |
<%= link_to_remote 'Remove last education history',
:update=>'education_history', :url => { :action =>'remove_education_history' },
:with => "Form.serialize($('resume_form'))" %>
<%end%>
<%# end Ajax links %>
<% @resume.education_histories.each_with_index do |education_histories,index| %>
<% fields_for "education_histories[#{index}]", education_histories do |education_histories_fields| %>
<p>Highest degree:<br/><%= education_histories_fields.text_field :highest_degree %></p>
<% fields_for "education_histories[#{index}][address_attr]",
@resume.education_histories[index].address do |@address_fields| %>
<%= render :partial => 'address'%>
<%end -%>
<%end -%>
...
_address.rhtml
...
<p>Street 1:<br/><%= @address_fields.text_field :street_1 %>
<p>Street 2:<br/><%= @address_fields.text_field :street_2 %>
...
So with this I'm able to build a complex form with nested collections which have nested objects. Most of the official Rails docs and books don't really touch on this complexity for their various reasons. I can say that this works for me and much thanks to Ryan for helping solve it.