Magically create association in ‘has_many :through’ relationship in Rails
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
endclass Contact < ApplicationRecord
has_many assignings, inverse_of: :contact, dependent: :destroy
endclass 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.