Magically create association in ‘has_many :through’ relationship in Rails

Robert Guiscard
2 min readDec 15, 2020

--

You may know fields_for and accepts_nested_attributes_for already to input nested attributes. It is convenient but rather complicated way to create association between models. For a more simple situation where you have to add or remove association between two models in a has_many :through relationship, there is a magical way which may suit you.

Here is the relationships:

class Task < ApplicationRecord
has_many assignings, inverse_of: :task, dependent: :destroy
has_many assignees, through: :assignings, source: :contact
end
class Contact < ApplicationRecord
has_many assignings, inverse_of: :contact, dependent: :destroy
end
class Assigning < ApplicationRecord
belongs_to :contact, inverse_of: :assignings
belongs_to :task, inverse_of: :assignings
end

For any given task, it can have many assignees. I intentionally use Contact as model name to demonstrate the name convention in this case.

Now, in the task editing form, I would like to have a list of check boxes to select assignees and update (or delete) the relationship (assigning model).

We can use collection_check_box for this purpose.

= f.label :task_name
= f.text_field :task_name
= f.label :assignees
= f.collection_check_boxes :assignee_ids, current_user.contacts, :id, :name

And allow assignee_ids as part of parameters in controller

def task_params
params.require(:task).permit(:task_name, :assignee_ids => [])
end

You can image that once we receive params[:task][:assignee_ids] in task controller, we need to create new associations for new assignees and remove old ones. It would looks like this:

old_ids = task.assignees.pluck(:id)
new_ids = params[:task][:assignee_ids].compact.collect(&:to_i)
(new_ids-old_ids).each do |x|
task.assignings.create(contact_id: x)
end
(old_ids-new_ids).each do |x|
task.assignings.find_by(contact_id: x).destroy
end

But actually you need to do nothing. Yes, NOTHING ! task.update(task_params) will work it out. Just be sure that assignee_ids in form (parameters) match the assocation name has_many assignees .

I do not know when this style of update is supported. But it works in Rails 6.1.

--

--

Responses (1)