Trying to Use Accepts_Nested_Attributes_For and Has_And_Belongs_To_Many But the Join Table Is Not Being Populated

Trying to use accepts_nested_attributes_for and has_and_belongs_to_many but the join table is not being populated

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:

  1. Create a join model, and change the relationship to a has many through one, accepting nested attributes on the join model.
  2. Make a minor adjustment in the controller, in terms of building things.
  3. Pass the form builder object to the partial.
  4. 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>

HABTM and accepts_nested_attributes_for

I'm not sure why so many people use has_and_belongs_to_many, which is a relic from Rails 1, instead of using has_many ..., :through except that it's probably in a lot of old reference books and tutorials. The big difference between the two approaches is the first uses a compound key to identify them, the second a first-class model.

If you redefine your relationship, you can manage on the intermediate model level. For instance, you can add and remove BookAuthor records instead of has_and_belongs_to_many links which are notoriously difficult to tweak on an individual basis.

You can create a simple model:

class BookAuthor < ActiveRecord::Base
belongs_to :book
belongs_to :author
end

Each of your other models is now more easily linked:

class Book < ActiveRecord::Base
has_many :book_authors
has_many :authors, :through => :book_authors
end

class Author < ActiveRecord::Base
has_many :book_authors
has_many :books, :through => :book_authors
end

On your nested form, manage the book_authors relationship directly, adding and removing those as required.

nested form & habtm

Instead of using accepts_nested_attributes_for, have you considered just adding the user to the group in your controller? That way you don't need to pass user_group_id back and forth.

In users_controller.rb:

def create
@user = User.new params[:user]
@user.user_groups << UserGroup.find(group_id_you_wanted)
end

This way you'll also stop people from doctoring the form and adding themselves to whichever group they wanted.

has_and_belongs_to_many in Rails

has_and_belongs_to_many is meant for simple many-to-many relationships.

has_many :through, on the other hand, is meant for indirect one-to-many relationships, or many-to-many relationships with properties.

If you're only looking for a simple many-to-many relationship, I can't see any reason not to use has_and_belongs_to_many.

Example many-to-many relationship:

User belongs to zero or more groups, and group has zero or more members (users).

Example many-to-many relationship with properties:

User belongs to zero or more groups, and group has zero or more members with ranks.

For example, Alice might be an Administrator in Group A, and a Moderator in Group B. You can hold this property in the join table.

Example indirect one-to-many relationship:

A category has zero or more sub-categories, and each sub-category has zero or more items.

A category therefore has zero or more items through its sub-categories.

Consider these categories:

Food → Fruits, Vegetables

Fruits → Apple, Orange, etc.

Vegetables → Carrot, Celery, etc.

therefore:

Food → Apple, Orange, Carrot, Celery, etc.

Option from collection_select creates a new one on submit - Rails 5

A really common newbie misconception is that you need nested attributes to assign associations.
Nested attributes is used to create brand new categories (or edit existing ones) from the same form as the plugin and is usually best avoided.

Remember here that there is huge difference between categories and rows on the categories_plugins join table. You want to create the the later.

All you really need to to do is use the _ids setter / getter created by has_and_belongs_to_many.

class Plugin
has_and_belongs_to_many :categories
end
<%= form_with(model: @plugin) do |f| %>
# ...
<%= c.collection_select :category_ids, Category.order(:name), :id, :name, multiple: true, include_blank: true %>
# ...
<% end %>
def plugin_params
params.require(:plugin).permit(:name, :url, :image, :description, category_ids: [])
end

The category_ids= setter will handle inserting/deleting rows into the categories_plugins join table automatically.

How to add additional data into join table for has_and_belongs_to_many relation?

You can't. If you want to add attributes to the join table, you need to make into a model, and then use :through on the relationships to achieve the same thing (this is actually my favorite way of implementing habtm relations):

class ProviderUser < ActiveRecord::Base
belongs_to :user
belongs_to :provider

validates_presence_of :uid
end

class User < ActiveRecord::Base
has_many :provider_users
has_many :providers, :through=>:provider_users
end

class Provider < ActiveRecord::Base
has_many :provider_users
has_many :users, :through=>:provider_users
end


Related Topics



Leave a reply



Submit