James West
2009-Aug-07 03:56 UTC
has_many :through, multi model form unable to save new child
I am totally unable to create a new child in a has_many :through multi model form using nested_attributes for an existing grandparent. Sorry, that sounds quite a mouth full. Basically I am editing an existing user that has many administrator_addresses through an administrator association. If I create a new address for the user I get various errors related to the association between users(grandparent) and administrator_addresses(grandchild) and I''m wondering how the new address record(grandchild) get''s the administrator (parent) information? Grandparent <- Parent <- child Grandparent has_many :grand_children, :through => :parent, :source => :child If the child validates_presence_of :parent_id I get an error parent must not be blank. If I don''t validate the parent id I receive an error stating Cannot modify association ''User#administrator_addresses'' because the source reflection class ''Address'' is associated to ''Administrator'' via :has_many. which makes sense if the parent_id has not been passed to the params hash for the create method. So how to set the parent ID on the child on a has_many :through multi model form? I''m wondering if I have missed something obvious. Models class User < ActiveRecord::Base named_scope :find_administrators, :joins => :administrator has_one :administrator, :dependent => :destroy has_many :administrator_addresses, :through => :administrator, :source => :addresses, :dependent => :destroy accepts_nested_attributes_for :administrator_addresses, :allow_destroy => true ... end class Administrator < UserRole belongs_to :user has_one :site_contact, :foreign_key => :user_role_id has_many :addresses, :foreign_key => :contact_id, :dependent => :destroy end class Address < ActiveRecord::Base belongs_to :administrator, :foreign_key => :contact_id validates_presence_of :contact_id validates_presence_of :house end class UserRole < ActiveRecord::Base end The link helper to build the nested form (slightly different from you usage) def add_address_link(form_builder) link_to_function ''New address'' do |page| form_builder.fields_for :administrator_addresses, Address.new, :child_index => ''NEW_RECORD'' do |f| html = render(:partial => ''address_form'', :locals => { :f => f }) page << "jQuery(''#maintenance_form'').replaceWith(''#{escape_javascript(html)}''.replace(/NEW_RECORD/g, new Date().getTime()))" end end end The _address_form.html.erb <fieldset> <legend>Details...</legend> <p> House name/number<br /> <%= f.text_field :house %> </p> <p> Street<br /> <%= f.text_field :street %> </p> <p> Town<br /> <%= f.text_field :town %> </p> <p> city<br /> <%= f.text_field :city %> </p> <p> Postcode<br /> <%= f.text_field :postcode %> </p> <%= link_to_function ''Done'' %> </fieldset> The params hash shows {"user"=>{"name"=>"test2", "public_name"=>"Test 2", "password_confirmation"=>"", "administrator_addresses_attributes"=>{"1249586125635"=>{"city"=>"fff", "postcode"=>"fff", "house"=>"fff", "street"=>"fff", "town"=>"ffff"}}, "password"=>""}, "commit"=>"Update", "_method"=>"put", "authenticity_token"=>"rKa5WGzAcoSDRgBtTA1/RSFp5k4pKQhTCWp1fTRiq6o=", "id"=>"1"} The update method in the user controller def update logger.debug("#### update params = #{params}") @user = User.find(params[:id]) respond_to do |format| if @user.update_attributes(params[:user]) flash[:notice] = "#{show_user_type} was successfully updated." format.html { redirect_to([:admin, @user]) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @user.errors, :status => :unprocessable_entity } end end end Any ideas would be really cool! Thanks -- Posted via http://www.ruby-forum.com/.
James West
2009-Aug-07 11:21 UTC
Re: has_many :through, multi model form unable to save new child
Further testing shows that if I use a hidden contact_id field in the address form the contact_id gets passed to the params hash as I would expect but I still get the Cannot modify association ''User#administrator_addresses'' because the source reflection class ''Address'' is associated to ''Administrator'' via :has_many. error! I''m totally stumped and this is becoming rather critical so if anyone has any work arounds I''d appreciate it. -- Posted via http://www.ruby-forum.com/.
Ilan Berci
2009-Aug-07 15:29 UTC
Re: has_many :through, multi model form unable to save new child
James West wrote:> Further testing shows that if I use a hidden contact_id field in the > address form the contact_id gets passed to the params hash as I would > expect but I still get the >You either have to change: has_one :administrator, :dependent => :destroy to: has_many :administrators, :dependent => :destroy or remove the administrator_addresses association and do this.. def administrator_addresses administrator.addresses end your through relation should go through a ''has_many'' and not a ''has_one''.. hth ilan -- Posted via http://www.ruby-forum.com/.
James West
2009-Aug-07 20:58 UTC
Re: has_many :through, multi model form unable to save new child
Ilan Berci wrote:> James West wrote: >> Further testing shows that if I use a hidden contact_id field in the >> address form the contact_id gets passed to the params hash as I would >> expect but I still get the >> > > > You either have to change: > > has_one :administrator, > :dependent => :destroy > > > > to: > has_many :administrators, > :dependent => :destroy > > > or remove the administrator_addresses association and do this.. > > def administrator_addresses > administrator.addresses > end > > your through relation should go through a ''has_many'' and not a > ''has_one''.. > > hth > > ilanThank you for your reply, I have changed my association because what you say makes total sense however I''m still getting the same error. I''m starting to think that the accepts_nested_attributes is broken. I now have in my models. class User < ActiveRecord::Base named_scope :find_administrators, :joins => :administrator has_one :administrator, :dependent => :destroy has_many :user_roles, :dependent => :destroy has_many :addresses, :through => :user_roles, :source => :addresses, :foreign_key => :contact_id, :dependent => :destroy accepts_nested_attributes_for :addresses, :allow_destroy => true ... end class UserRole < ActiveRecord::Base has_many :addresses, :foreign_key => :contact_id, :dependent => :destroy has_many :emails, :foreign_key => :contact_id, :dependent => :destroy belongs_to :user accepts_nested_attributes_for :addresses, :allow_destroy => true end class Address < ActiveRecord::Base belongs_to :user_role, :foreign_key => :contact_id validates_presence_of :contact_id validates_presence_of :house end class Administrator < UserRole #To do - Move this to a boolean flag on the user_role table. has_one :site_contact, :foreign_key => :user_role_id end Relevant controller methods class Admin::UsersController < Admin::HomeController # GET /users/1/edit def edit @user = User.find(params[:id]) end # PUT /users/1 # PUT /users/1.xml def update logger.debug("#### update params = #{params}") @user = User.find(params[:id]) respond_to do |format| if @user.update_attributes(params[:user]) flash[:notice] = "#{show_user_type} was successfully updated." format.html { redirect_to([:admin, @user]) } format.xml { head :ok } else format.html { render :action => "edit" } format.xml { render :xml => @user.errors, :status => :unprocessable_entity } end end end Helper methods # # Support methods for nested forms # def link_to_toggle_child_list(div_to_toggle, link_text) link_to_function link_text do |page| page << "jQuery(''#{div_to_toggle}).toggle(''slow'')" end end def add_address_link(form_builder) link_to_function ''New address'' do |page| form_builder.fields_for :addresses, Address.new, :child_index => ''NEW_RECORD'' do |f| html = render(:partial => ''address_form'', :locals => { :f => f }) page << "jQuery(''#maintenance_form'').replaceWith(''#{escape_javascript(html)}''.replace(/NEW_RECORD/g, new Date().getTime()))" end end end end Views _form.html.erb <%= f.error_messages -%> <div class="admin-form"> <fieldset> <legend><%= show_user_type %></legend> <p> <%= f.label :public_name -%>: <%= f.text_field :public_name -%> </p> <p> <%= f.label :name -%>: <%= f.text_field :name -%> </p> <p> <%= f.label :password, ''Password'' -%>: <%= f.password_field :password, :size => 40-%> </p> <p> <%= f.label :password_confirmation, "confirm" -%>: <%= f.password_field :password_confirmation, :size => 40-%> </p> </fieldset> </div> <%= link_to_function "Show/Hide addresses", {:id => "toggle_children"}%> div id ="address_list"> <%= add_address_link(f) %> <%= show_address_list(@user) %> </div> <div id ="maintenance_form"> </div> _address_form.html.erb <%= f.hidden_field :contact_id%> <fieldset> <legend>Details...</legend> <p> House name/number<br /> <%= f.text_field :house %> </p> <p> Street<br /> <%= f.text_field :street %> </p> <p> Town<br /> <%= f.text_field :town %> </p> <p> city<br /> <%= f.text_field :city %> </p> <p> Postcode<br /> <%= f.text_field :postcode %> </p> <%= link_to_function ''Done'' %> </fieldset> -- Posted via http://www.ruby-forum.com/.
James West
2009-Aug-07 21:06 UTC
Re: has_many :through, multi model form unable to save new child
James West wrote: Sorry, I ommited to post the actual error which is now Cannot modify association ''User#addresses'' because the source reflection class ''Address'' is associated to ''UserRole'' via :has_many. -- Posted via http://www.ruby-forum.com/.
James West
2009-Aug-08 06:51 UTC
Re: has_many :through, multi model form unable to save new child
James West wrote:> James West wrote: > Sorry, I ommited to post the actual error which is now > > Cannot modify association ''User#addresses'' because the source reflection > class ''Address'' is associated to ''UserRole'' via :has_many.Further investigation shows that the address record seems to have the correct SQL being generated for it. I just updated Rails from 2.3.2 to 2.3.3 to see if that made a difference but this has had no effect. Processing Admin::UsersController#update (for 127.0.0.1 at 2009-08-08 07:37:27) [PUT] Parameters: {"user"=>{"name"=>"test2", "public_name"=>"Test 2", "addresses_attributes"=>{"1249713438151"=>{"city"=>"ccc", "postcode"=>"ccc", "house"=>"ccc", "town"=>"ccc", "street"=>"ccc"}}, "password_confirmation"=>"", "password"=>""}, "commit"=>"Update", "authenticity_token"=>"xO3gOpJcO08UGEzEtifnCIKqEOhwtDaz0UkM7X7aAQY=", "id"=>"1"} [4;36;1mUser Load (0.0ms) [0m [0;1mSELECT * FROM "users" WHERE ("users"."id" = 1) LIMIT 1 [0m [4;35;1mTheme Load (0.0ms) [0m [0mSELECT * FROM "themes" WHERE ("themes"."active" = ''t'') LIMIT 1 [0m ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection (Cannot modify association ''User#addresses'' because the source reflection class ''Address'' is associated to ''UserRole'' via :has_many.): app/controllers/admin/users_controller.rb:72:in `update'' app/controllers/admin/users_controller.rb:71:in `update'' C:/Development/InstantRails/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'' C:/Development/InstantRails/ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'' C:/Development/InstantRails/ruby/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'' C:/Development/InstantRails/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start'' C:/Development/InstantRails/ruby/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'' C:/Development/InstantRails/ruby/lib/ruby/1.8/webrick/server.rb:95:in `start'' C:/Development/InstantRails/ruby/lib/ruby/1.8/webrick/server.rb:92:in `each'' C:/Development/InstantRails/ruby/lib/ruby/1.8/webrick/server.rb:92:in `start'' C:/Development/InstantRails/ruby/lib/ruby/1.8/webrick/server.rb:23:in `start'' C:/Development/InstantRails/ruby/lib/ruby/1.8/webrick/server.rb:82:in `start'' Rendered rescues/_trace (484.0ms) Rendered rescues/_request_and_response (0.0ms) Rendering rescues/layout (internal_server_error) -- Posted via http://www.ruby-forum.com/.
James West
2009-Aug-08 15:38 UTC
Re: has_many :through, multi model form unable to save new child
OK this is a Rails bug. I have done extensive testing now and am able to prove that the code works on a has many but has_many :through fails. I''ll report it as a bug if someone can tell me how? Thanks James -- Posted via http://www.ruby-forum.com/.