A common area where developers can get hung up is when using
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
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
To be able to pass in a hash with
user_groups_attributes to the
Group model and call
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
Group would look like this:
class Group < ActiveRecord::Base has_many :user_groups has_many :users, through: :groups accepts_nested_attributes_for :user_groups end
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.