Trying to use accepts_nested_attributes_for and has_and_belongs_to_many but the join table is not being populated
has-and-belongs-to-many, ruby, ruby-on-rails
Solution
You two have problems here, unfortunately one of them is masked by the other.
Both problems stem from this part of the view:
<div class="association">
<% fields_for :associations do |assoc_form| %>
<%= assoc_form.collection_select(:association_id, Association.find(:all),
:id, :name, :include_blank => true) %>
Problem 1: You've misunderstood what accept_nested_fields_for does.
accepts_nested_fields_for is used to create and modify related objects in a form. It can be used to populate join table, which is kind of what you're trying to do. However, using accepts_nested_fields_for to populate the join table is impossible with a HABTM relationship. A good use of accepts_nested_fields_for would be if you wanted to create a new Association that will be linked with the new Attorney. Or if you had a rich join model that required additional information for each record.
Problem 2: You're not linking the fields in this form to the attorney form. Which is necessary to use accepts_nested_fields_for.
We've already established that accepts_nested_fields_for is not what you need to accomplish this, but, you're still not associating the select association_id field with the form. Which is why params[associations][association_id] was set and not params[attorney][associations][association_id].
Problem 3: The form structure is all wrong for what it looks like you're trying to accomplish.
There's a too much that needs correcting for me to give a proper break down. You're better off checking out the complex-forms-example repository. It's a working example of accepts_nested_attributes_for, it doesn't deal with any HABTM relationships, but it should teach you every thing you need to know. The corrected code below is 90 % of what you need. The complex-forms-examples linked above will teach you what you need to know to fill in the blanks that are add_association_link and create_association_link.
The correction involves the following steps:
- Create a join model, and change the relationship to a has many through one, accepting nested attributes on the join model.
- Make a minor adjustment in the controller, in terms of building things.
- Pass the form builder object to the partial.
- Rewrite the form in the partial so it is focuses on the newly created join model.
You can accomplish this with the following changes.
class Attorney < ActiveRecord::Base
has_many :attorney_associations
has_many :associations, :through => :attorney_associations
accepts_nested_attributes_for :attorney_associations, :reject_if => proc { |a|
a['association_id'].blank? }
accepts_nested_attributes_for :associations, :reject_if => proc {|a|
a['name'].blank?}
end
class AttorneyAssociations < ActiveRecord::Base
belongs_to :attorney
belongs_to :association
end
Attorney Controller:
def new
@attorney = Attorney.new
@attorney.associations.build
@attorney.attorney_associations.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @attorney }
end
end
New Attorney View:
<% form_for(@attorney, :html => {:multipart => true}) do |f| %>
<%= f.error_messages %>
<%= f.label :"First name" %>
<%= f.text_field :firstname %><br>
<%= f.label :"Last Name" %>
<%= f.text_field :lastname %><br>
<%= f.label :"Attorney Type" %>
<%= f.collection_select :member_type_id, MemberType.all, :id, :name %><br>
<%= f.text_area :bio, :cols => 70, :rows => 20 %><br><br>
<%= f.label :"Attorney Location" %>
<%= f.collection_select :office_location_id, OfficeLocation.all, :id, :location %><br>
<div id="associations">
<% f.fields_for :attorney_association do |aa_form| %>
<%= render :partial => 'attorney_association', :locals => {:f => aa_form} %>
<% end %>
<%= add_association_link "Add Another Existing Association" %>
<% f.fields_for :associations do |assoc_form| %>
<%= render :partial => 'attorney', :locals => {:f => assoc_form} %>
<%= create_association_link, "Create a New Association for this Attorney" %>
</div>
<%= f.submit 'Create' %>
<% end %>
I'm assuming that add_association_link is a javascript helper that creates a link to clone an empty instance of what was the Membership partial. create_association_link is a place holder for a similar helper that will add a partial for a new association.
Attorney Association Partial:
<div class="attorney_association">
<%= f.collection_select(:association_id, Association.find(:all),
:id, :name, :include_blank => true) %>
<%= link_to_function "remove", "$(this).up('.attorney_association').remove()" %>
</div>
Association Partial:
<div class="association">
<%= f.label_for :name %>
<%= f.text_field :name %>
<%= link_to_function "remove", "$(this).up('.attorney_association').remove()" %>
</div>
Problem
I am learning RoR and trying to use accepts_nested_attributes_for and has_and_belongs_to_many to submit information that would traditionally be two forms. I have read on some sites they are compatible, some sites they aren't compatible, and some sites don't know. As reference, I am using Rails 2.3.4. I tried modeling my solution from the Ryan's Scraps tutorial on nested models From what I have tried to debug, it seems that I have two problems but I am not sure why. - When I submit a form with nested models, only part of the nested model information is posted. I only get the first field, not the "n" others the user may have selected - Of the single field that gets posted, there aren't any rows inserted into the join table that I created for the HABTM relationship. Here is a piece of code and the corresponding logs for my insertion attempt: Attorney Model: ``` class Attorney < ActiveRecord::Base has_and_belongs_to_many :associations accepts_nested_attributes_for :associations, :reject_if => proc { |a| a['name'].blank? } end ``` Association Model: ``` class Association < ActiveRecord::Base has_and_belongs_to_many :attorneys accepts_nested_attributes_for :attorneys validates_presence_of :name, :message => "Please enter an association name." end ``` Attorneys Controller: ``` def new @attorney = Attorney.new @attorney.associations.build respond_to do |format| format.html # new.html.erb format.xml { render :xml => @attorney } end end def create @attorney = Attorney.new(params[:attorney]) respond_to do |format| if @attorney.save flash[:notice] = 'Attorney was successfully created.' format.html { redirect_to(@attorney) } format.xml { render :xml => @attorney, :status => :created, :location => @attorney } else format.html { render :action => "new" } format.xml { render :xml => @attorney.errors, :status => :unprocessable_entity } end end end ``` Attorney's New View: ``` <% form_for(@attorney, :html => {:multipart => true}) do |f| %> <%= f.error_messages %> <%= f.label :"First name" %> <%= f.text_field :firstname %><br> <%= f.label :"Last Name" %> <%= f.text_field :lastname %><br> <%= f.label :"Attorney Type" %> <%= f.collection_select :member_type_id, MemberType.all, :id, :name %><br> <%= f.text_area :bio, :cols => 70, :rows => 20 %><br><br> <%= f.label :"Attorney Location" %> <%= f.collection_select :office_location_id, OfficeLocation.all, :id, :location %><br> <div id="associations"> <%= render :partial => 'shared/membership' %> </div> <%= add_association_link "Add Association" %> <%= f.submit 'Create' %> <% end %> ``` Membership Partial: ``` <div class="association"> <% fields_for :associations do |assoc_form| %> <%= assoc_form.collection_select(:association_id, Association.find(:all), :id, :name, :include_blank => true) %> ``` <%= link_to_function "remove", "$(this).up('.association').remove()" %> <%= link_to 'New Association', new_association_path %> <% end %> Attorney Helper Link: ``` def add_association_link(name) link_to_function name do |page| page.insert_html :bottom, :associations, :partial => 'shared/membership', :object => AssociationsAttorneys.new end end ``` Join Table Migration: ``` class CreateAssociationsAttorneys < ActiveRecord::Migration def self.up create_table :associations_attorneys do |t| t.references :attorney, :null => false t.references :association, :null => false t.timestamps end end def self.down drop_table :associations_attorneys end end ``` Log capture: ``` Processing AttorneysController#new (for 127.0.0.1 at 2009-12-04 08:16:19) [GET] Rendering template within layouts/default Rendering attorneys/new [4;35;1mMemberType Load (0.4ms)[0m [0mSELECT * FROM "member_types" [0m [4;36;1mOfficeLocation Load (18.6ms)[0m [0;1mSELECT * FROM "office_locations" [0m [4;35;1mAssociation Load (0.6ms)[0m [0mSELECT * FROM "associations" [0m Rendered shared/_membership (3.5ms) [4;36;1mCACHE (0.0ms)[0m [0;1mSELECT * FROM "associations" [0m Rendered shared/_membership (1.5ms) Rendered shared/_nav (0.6ms) Rendered shared/_footer (0.1ms) Completed in 149ms (View: 114, DB: 20) | 200 OK [http://localhost/attorneys/new] Processing ApplicationController#index (for 127.0.0.1 at 2009-12-04 08:16:19) [GET] Processing AttorneysController#create (for 127.0.0.1 at 2009-12-04 08:16:57) [POST] Parameters: {"commit"=>"Create", "authenticity_token"=>"Jh7aMCcOY7jUu/D1YtiCswg2n6iwqnS98VnVn46psp0=", "associations"=>{"association_id"=>"3"}, "attorney"=>{"birthstate"=>"Alabama", "office_location_id"=>"1", "birthdate"=>"December 3, 2009", "birthcity"=>"Test", "middlename"=>"Test", "lastname"=>"Testing", "image_temp"=>"", "member_type_id"=>"2", "firstname"=>"Test", "bio"=>"testing testing testing", "suffix"=>"", "email"=>"testing@test.com"}} [4;35;1mAttorney Load (15.6ms)[0m [0mSELECT "attorneys".id FROM "attorneys" WHERE ("attorneys"."email" = 'testing@test.com') LIMIT 1[0m [4;36;1mAttorney Create (0.8ms)[0m [0;1mINSERT INTO "attorneys" ("birthstate", "created_at", "birthdate", "office_location_id", "birthcity", "updated_at", "middlename", "lastname", "firstname", "member_type_id", "suffix", "bio", "image", "email") VALUES('Alabama', '2009-12-04 15:16:57', 'December 3, 2009', 1, 'Test', '2009-12-04 15:16:57', 'Test', 'Testing', 'Test', 2, '', 'testing testing testing', NULL, 'testing@test.com')[0m Redirected to http://localhost:3000/attorneys/11 Completed in 150ms (DB: 16) | 302 Found [http://localhost/attorneys] ``` I can see that associations"=>{"association_id"=>"3"} it is only getting the last of the multiple associations that I had for the particular person and it isn't creating any entries into the join table. Where might my code have gone wrong?