Ryan Tinsley
2009-Oct-14 05:10 UTC
Render partials with nested attributes from another model
Hi everyone, My previous question was posted here<http://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 <http://github.com/ryanb/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 \"#...@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) > 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*> <div id="addRooms"> > <p>House id is <%= @house.id %></p> > > <h3>Your rooms:</h3> > <% if @house.rooms %> > <ul> > <% for room in @house.rooms %> > <li> > <%= h 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 --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Ryan Tinsley
2009-Oct-14 05:48 UTC
Render partials with nested attributes from another model
Hi everyone,
My previous question was posted
here<http://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 \"#...@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)
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;<br>
&lt;p&gt;House id is
&lt;%= @house.id
%&gt;&lt;/p&gt;<br><br>
&lt;h3&gt;Your rooms:&lt;/h3&gt;<br>
&lt;% if
@house.rooms %&gt;<br> &lt;ul&gt;<br>
&lt;% for room in
@house.rooms %&gt;<br> &lt;li&gt;<br>
&lt;%= h
room.name%&gt; has &lt;%= h room.number_of_bulbs
%&gt;<br>
&lt;%= h
room.wattage_of_bulbs %&gt; watt bulbs, in use for<br>
&lt;%= h
room.usage_hours %&gt; hours per day.<br>
&lt;/li&gt;<br>
&lt;% end %&gt;<br> &lt;/ul&gt;<br>
&lt;% else
%&gt;<br> &lt;p&gt;You have not added any rooms
yet&lt;/p&gt;<br> &lt;% end
%&gt;<br><br> &lt;%= render
:partial =&gt; ''rooms/room_form''
%&gt;<br><br> &lt;br
/&gt;<br> &lt;%= button_to "Continue to report",
:action =&gt;
"report", :id =&gt; @house
%&gt;<br>&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
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Ruby on Rails: Talk" group.
To post to this group, send email to
rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org
To unsubscribe from this group, send email to
rubyonrails-talk+unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org
For more options, visit this group at
http://groups.google.com/group/rubyonrails-talk?hl=en
-~----------~----~----~----~------~----~------~--~---