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' %>

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

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.

James W. 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

James W. 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.

Ilan B. wrote:

James W. 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

Thank 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 -%>

<%= show_user_type %>

<%= f.label :public_name -%>: <%= f.text_field :public_name -%>

<%= f.label :name -%>: <%= f.text_field :name -%>

<%= f.label :password, 'Password' -%>: <%= f.password_field :password, :size => 40-%>

<%= f.label :password_confirmation, "confirm" -%>: <%= f.password_field :password_confirmation, :size => 40-%>

<%= link_to_function "Show/Hide addresses", {:id => "toggle_children"}%>

div id =“address_list”>
<%= add_address_link(f) %>
<%= show_address_list(@user) %>

_address_form.html.erb

<%= f.hidden_field :contact_id%>

Details...

House name/number
<%= f.text_field :house %>

Street
<%= f.text_field :street %>

Town
<%= f.text_field :town %>

city
<%= f.text_field :city %>

Postcode
<%= f.text_field :postcode %>

<%= link_to_function 'Done' %>

James W. wrote:

James W. 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”}
e[4;36;1mUser Load (0.0ms)e[0m e[0;1mSELECT * FROM “users” WHERE
(“users”.“id” = 1) LIMIT 1e[0m
e[4;35;1mTheme Load (0.0ms)e[0m e[0mSELECT * FROM “themes” WHERE
(“themes”.“active” = ‘t’) LIMIT 1e[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:inupdate’
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:inrun’
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:instart’
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:instart’
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:instart’
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:instart’

Rendered rescues/_trace (484.0ms)
Rendered rescues/_request_and_response (0.0ms)
Rendering rescues/layout (internal_server_error)

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