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:
- 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>
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
How to Format a Number 1000 as "1 000"
Ruby - Keyword Arguments - Can You Treat All of the Keyword Arguments as a Hash? How
Millisecond Resolution of Datetime in Ruby
Ruby Gem Not Found Although It Is Installed
Is It a Bad Practice to List Ruby Version in Both Gemfile and .Ruby-Version Dotfile
Activerecord Select Except Columns
Heroku Rails 4 Could Not Connect to Server: Connection Refused
Ruby Dynamic Classes. How to Fix "Warning: Class Variable Access from Toplevel"
Properly Converting a Cmyk Image to Rgb with Rmagick
Accessing Objects Memory Address in Ruby
Is There an Equivalent to 'Array::Sample' for Hashes
Consistent String#Hash Based Only on the String's Content