Render partials with nested attributes from another model

Hi everyone,

My previous question was posted here. 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](http://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 \"#...@[room.name](http://room.name)}\" 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](http://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

  <p>House id is <%= @[house.id](http://house.id) %></p>


  <h3>Your rooms:</h3>
  <% if @house.rooms %>

  <ul>
    <% for room in @house.rooms %>

    <li>
      <%= h [room.name](http://room.name) %> has <%= h room.number_of_bulbs %>

      <%= h room.wattage_of_bulbs %> watt bulbs, in use for
      <%= h room.usage_hours %> hours per day.

    </li>
    <% end %>

  </ul>
  <% else %>

  <p>You have not added any rooms yet</p>
  <% end %>


  <%= render :partial => 'rooms/room_form' %>


  <br />
  <%= button_to "Continue to report", :action => "report", :id => @house %>

</div>

app/views/rooms/_room_form.html.erb

<% form_for :room, :url => { :action => :add_room, :id => @house } do |form| %> <%= form.error_messages %>

  <p>
    <%= form.label :name %><br />

    <%= form.text_field :name %>
  </p>

 
  <h3>Lights</h3>

  <% form.fields_for :lights do |light_form| %>
    <%= render :partial => 'rooms/light', :locals => { :form => light_form } %>

  <% end %>
  <p class="addLink"><%= add_child_link "[+] Add new light", form, :lights %></p>

 
  <h3>Small Appliances</h3>

  <% form.fields_for :small_appliances do |sm_appl_form| %>
    <%= render :partial => 'rooms/small_appliance', :locals => { :form => sm_appl_form } %>

  <% end %>
  <p class="addLink"><%= add_child_link "[+] Add new small appliance", form, :small_appliances %></p>

 
  <p><%= form.submit "Submit" %></p>

<% end %>

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