A common area where developers can get hung up is when using has_many :through
. has_many :through
essentially allows you to link two models together with a “join table”. Though it can be pretty simple to work with, things can get confusing when you find yourself having to pass and store additional attributes in this join table.
“I have read every post on the internet pertaining to has_many through with additional attributes on the join table and I am still not getting it”
“how do you store extra info in the join model and extract that out later?”
If you followed along my previous post, at this point you should have a good idea of how to write your models, controllers and views such that you can create, display and edit records which use a has_many :through
association. Make sure to check out Ryan Bates screencast on the basics, and also checkout Rahoul’s article on when you should has_many
in the first place.
So, what do you do when you want to store/access additional attributes in the join table?
You already know how to set up your view form so that it passes in parameters to your controller and correctly creates a new record in the join table. If you’re using checkboxes in your view, the checkbox code probably looks like this:
<% Group.all.each do |group| %>
<%= check_box_tag "user[group_ids][]", group.id, @user.group_ids.include?(group.id) %>
<%= group.name %>
<br />
<% end %>
…assuming that you have a User
and Group
models and a UserGroup
which functions as the join.
And your controller #create
action should be very similar to this:
def create
User.create!(user_params)
end
def user_params
params.require(:user).permit(group_ids: [])
end
As you can see, we don’t have to do anything special in the controller because group_ids
will be accepted as a parameter (it is part of the dynamic programming which happens when you call has_many
). You do have to correctly permit the group_ids
param though.
Now, let’s say for a given user you want to specify if they are an admin of the group or not, via checkbox.
I’m going to assume that we will have an edit page for each group record, and on this page, we will see all the users that belong to this group along with a checkbox next to each user indicating if they are an admin or not.
In my group edit page, I want to show the users that are in the group and next to each user show a checkbox allowing me to select if the user is going to be an admin.
<%= form_for @group do |f| %>
<%= @group.inspect %>
<br />
<% if @group.user_groups.present? %>
<%= f.fields_for :user_groups do |ugf| %>
<% user = ugf.object.user %>
<%= user.name %>
Admin?
<%= ugf.check_box :admin %>
<br />
<% end %>
<% else %>
No Users in this group yet
<% end %>
<%= f.submit 'Update group' %>
<% end %>
Couple of things to note here:
1) I’m using the user_groups
association. By using fields_for
on this association, I can treat it like any other association of group
and build a custom form for it.
2) When submitting this form, the user_groups
parameters will be passed in under the user_groups_attributes
key in the params
hash.
To be able to pass in a hash with user_groups_attributes
to the Group
model and call update
or save
on it, we need to use the accepts_nested_attributes_for
method. This method tells Rails and ActiveRecord how to correctly deal with user_groups_attributes
being in the params
hash.
Our Group
would look like this:
<div class="d-flex flex-justify-center">
<img src="/assets/images/filter.jpeg" alt="Barista Ben" style="width: 50%; height: 50%"/>
</div>
So the parameter we pass in to accepts_nested_attributes_for
is the model/association we want to accept nested attributes for.
Because of the setup we did above in the Group
model, we can now use the usual update
method with the params that we get from the view/form.
def update
@group = Group.find(params[:id])
@group.update(group_params)
end
def group_params
params.require(:group).permit(user_groups_attributes: [:admin, :id])
end
If you’re using Rails 4 and strong_parameters
, you will have to make sure you permit the correct parameters.
And that’s it! You should now be able to update this admin attribute on the UserGroup
join table. You can follow the same approach for different types of data as well (like a text field for example). I encourage you to look deeper into what the params hash looks like once it gets to the controller so that you get more comfortable with it. Play with this idea in Rails console as well to increase your confidence.
I’ve posted an example app on github – check it out if you need more info about how exactly to get this to work.