Render partials with nested attributes from another model

Hi everyone,

My previous question was posted
herehttp://groups.google.com/group/rubyonrails-talk/browse_thread/thread/dddaeb075789d4b5.
I’m starting a new topic as the problems have shifted.

I have a rails application that models a house. There is a house model
that
has_many rooms. A room has a house_id and a name. I’ve also used the
complex-form-examples to give room nested attributes of lights and
small_appliances. complex-form-examples uses RJS and partials to
accomplish
this.

There is a controller called calculator that is what users will use to
access the application. When the submit button on calculator is pressed,
it
saves house information and redirects to an add_rooms page (located in
app/views/calculator/add_rooms.html.erb) where the user can add rooms to
the
house. The add_rooms page uses a partial from
app/views/rooms/_room_form.html.erb. I can get this page to display by
removing the add_child_link links. If I don’t remove them, I get this
error:

Showing app/views/rooms/_room_form.html.erb where line #13 raised:

undefined method `reflect_on_association’ for NilClass:Class

With add_child_link gone the page renders. However, when I click submit
I
get a new error message:

ActiveRecord::AssociationTypeMismatch in CalculatorController#add_room

SmallAppliance(#49096610) expected, got Array(#1560620)

RAILS_ROOT: C:/Users/ryan/Downloads/react
Application Trace | Framework Trace | Full Trace

C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/associations/association_proxy.rb:263:in
raise_on_type_mismatch' C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/associations/association_collection.rb:320:in replace’
C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/associations/association_collection.rb:320:in
each' C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/associations/association_collection.rb:320:in replace’
C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/associations.rb:1322:in
small_appliances=' C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2744:in send’
C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2744:in
attributes=' C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2740:in each’
C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2740:in
attributes=' C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2438:in initialize’
C:/Users/ryan/Downloads/react/app/controllers/calculator_controller.rb:31:in
new' C:/Users/ryan/Downloads/react/app/controllers/calculator_controller.rb:31:in add_room’

If I remove the small_application part, the same thing happens for
light. I
think it has something to do with accepts_nested_attributes_for in the
room
model. I need to get this page working so that rooms can be added to a
house
without using the scaffold built pages. Users won’t have access to
those. I
also need the house id to be saved as house_id in the rooms table. Here
is
the code:

app/models/room.rb

class Room < ActiveRecord::Base
belongs_to :house
has_many :lights, :dependent => :destroy
has_many :small_appliances, :dependent => :destroy
validates_presence_of :name
accepts_nested_attributes_for :lights, :reject_if => lambda { |a|
a.values.all?(&:blank?) }, :allow_destroy => true
accepts_nested_attributes_for :small_appliances, :reject_if =>
lambda { |a| a.values.all?(&:blank?) }, :allow_destroy => true
end

app/models/house.rb

class House < ActiveRecord::Base
has_many :rooms

validation code not included

def add_room(room)
rooms << room
end

end

app/controllers/calculator_controller.rb

class CalculatorController < ApplicationController
def index
end

save_house is called when submit is

pressed in app/views/calculator/index.html.erb

def save_house
@house = House.new(params[:house])
respond_to do |format|
if @house.save
format.html { render :action => ‘add_rooms’, :id => @house }
format.xml { render :xml => @house, :status => :created,
:location => @house }
else
format.html { render :action => ‘index’ }
format.xml { render :xml => @house.errors, :status =>
:unprocessable_entity }
end
end
end

def add_rooms
@house = House.find(params[:id])
@rooms = Room.find_by_house_id(@house.id)

rescue ActiveRecord::RecordNotFound
logger.error(“Attempt to access invalid house #{params[:id]}”)
flash[:notice] = “You must create a house before adding rooms”
redirect_to :action => ‘index’
end

def add_room
@house = House.find(params[:id])
@room = Room.new(params[:room])

respond_to do |format|
  if @room.save
    @house.add_room(@room)
    @house.save
    flash[:notice] = "Room \"#[email protected]}\" was successfully 

added."
format.html { render :action => ‘add_rooms’ }
format.xml { render :xml => @room, :status => :created,
:location => @room }
else
format.html { render :action => ‘add_rooms’ }
format.xml { render :xml => @room.errors, :status =>
:unprocessable_entity }
end
end
rescue ActiveRecord::RecordNotFound
logger.error(“Attempt to access invalid house #{params[:id]}”)
flash[:notice] = “You must create a house before adding a room”
redirect_to :action => ‘index’
end

def report
flash[:notice] = nil
@house = House.find(params[:id])
@rooms = Room.find_by_house_id(@house.id)
rescue ActiveRecord::RecordNotFound
logger.error(“Attempt to access invalid house #{params[:id]}”)
flash[:notice] = “You must create a house before generating a
report”
redirect_to :action => ‘index’
end

end

app/views/calculator/add_rooms.html.erb
&lt;div id=“addRooms”&gt;
&lt;p&gt;House id is
&lt;%= @house.id %&gt;&lt;/p&gt;


&lt;h3&gt;Your rooms:&lt;/h3&gt;
&lt;% if
@house.rooms %&gt;
&lt;ul&gt;
&lt;% for room in
@house.rooms %&gt;
&lt;li&gt;
&lt;%= h
room.name%&gt; has &lt;%= h room.number_of_bulbs %&gt;

&lt;%= h
room.wattage_of_bulbs %&gt; watt bulbs, in use for
&lt;%= h
room.usage_hours %&gt; hours per day.
&lt;/li&gt;

&lt;% end %&gt;
&lt;/ul&gt;
&lt;% else
%&gt;
&lt;p&gt;You have not added any rooms
yet&lt;/p&gt;
&lt;% end %&gt;

&lt;%=
render
:partial =&gt; ‘rooms/room_form’ %&gt;

&lt;br
/&gt;
&lt;%= button_to “Continue to report”, :action
=&gt;
“report”, :id =&gt; @house %&gt;
&lt;/div&gt;

app/views/rooms/_room_form.html.erb

&lt;% form_for :room, :url =&gt; { :action =&gt;
:add_room, :id =&gt; @house } do |form| %&gt;
&lt;%= form.error_messages %&gt;
&lt;p&gt;
&lt;%= form.label :name %&gt;&lt;br /&gt;
&lt;%= form.text_field :name %&gt;
&lt;/p&gt;

&lt;h3&gt;Lights&lt;/h3&gt;
&lt;% form.fields_for :lights do |light_form| %&gt;
&lt;%= render :partial =&gt; ‘rooms/light’, :locals
=&gt; { :form =&gt; light_form } %&gt;
&lt;% end %&gt;
&lt;p class=“addLink”&gt;&lt;%= add_child_link “[+] Add
new light”, form, :lights %&gt;&lt;/p&gt;

&lt;h3&gt;Small Appliances&lt;/h3&gt;
&lt;% form.fields_for :small_appliances do |sm_appl_form|
%&gt;
&lt;%= render :partial =&gt; ‘rooms/small_appliance’,
:locals =&gt; { :form =&gt; sm_appl_form } %&gt;
&lt;% end %&gt;
&lt;p class=“addLink”&gt;&lt;%= add_child_link “[+] Add
new small appliance”, form, :small_appliances
%&gt;&lt;/p&gt;

&lt;p&gt;&lt;%= form.submit “Submit”
%&gt;&lt;/p&gt;
&lt;% end %&gt;

application_helper.rb

module ApplicationHelper
def remove_child_link(name, form)
form.hidden_field(:_delete) + link_to_function(name,
“remove_fields(this)”)
end

def add_child_link(name, form, method)
fields = new_child_fields(form, method)
link_to_function(name, h(“insert_fields(this, "#{method}",
"#{escape_javascript(fields)}")”))
end

def new_child_fields(form_builder, method, options = {})
options[:object] ||=
form_builder.object.class.reflect_on_association(method).klass.new
options[:partial] ||= method.to_s.singularize
options[:form_builder_local] ||= :form
form_builder.fields_for(method, options[:object], :child_index =>
“new_#{method}”) do |form|
render(:partial => options[:partial], :locals => {
options[:form_builder_local] => form })
end
end
end

public/javascripts/application.js

function insert_fields(link, method, content) {
var new_id = new Date().getTime();
var regexp = new RegExp(“new_” + method, “g”)
$(link).up().insert({
before: content.replace(regexp, new_id)
});
}

function remove_fields(link) {
var hidden_field = $(link).previous(“input[type=hidden]”);
if (hidden_field) {
hidden_field.value = ‘1’;
}
$(link).up(“.fields”).hide();
}

Thanks,
Ryan