How do I remove a has_many through association properly?

I'm ashamed to admit I've probably spent the better part of 3 days on Google and StackOverflow trying to find a solution to this problem. Since I haven't found anything that not only matches my use case, but that has also been answered, I figured that reddit is as good a resource as any.

So I have 3 models: Project, Task, and Assignment. Project (has_many) Tasks (:through) Assignments, and vice versa for Tasks. My new/edit forms have checkboxes for existing Tasks, so the user can select however many they want to add to a project. Adding Tasks through a checkbox works fine for both creating a Project and updating one. However, I cannot uncheck a box to remove a task that has been added to a project. When I uncheck a box and attempt to save my changes, I'm met with this error:

ActiveRecord::RecordNotFound in ProjectsController#update error:
Couldn't find Task with ID=28 for Project with ID=39.
def raise_nested_attributes_record_not_found!(association_name, record_id)

I believe I'm getting this error because AR is looking for a Task that has already been disassociated from the Project, but that's just a hunch. Thoughts?

My models are as follows:

class Project < ActiveRecord::Base has_many :assignments, dependent: :delete_all, inverse_of: :project has_many :tasks, :through => :assignments
accepts_nested_attributes_for :tasks, reject_if: :all_blank
accepts_nested_attributes_for :assignments, :allow_destroy => true


class Task < ActiveRecord::Base has_many :assignments, inverse_of: :task has_many :projects, :through => :assignments
accepts_nested_attributes_for :assignments


class Assignment < ActiveRecord::Base belongs_to :project, inverse_of: :assignments belongs_to :task, inverse_of: :assignments accepts_nested_attributes_for :project, :reject_if => :all_blank

My Project controller#update method:

def update
@project = Project.find(params[:id])
params[:project][:task_ids] ||= []
if @project.update_attributes(project_params)
flash[:success] = "Your project has been updated!"
redirect_to @project
render 'edit'

def project_params
params.require(:project).permit(:job_code, :task_ids => [],
[:id, :item, :description, :requirement, :complexity,
:est_time, :actual_time, :_destroy],
assignments_attributes: [:id, :_destroy, :task_id])

Where might I be going wrong? I've been working on this problem for so long, I don't even know where to look anymore. Really appreciate any help/insight/solutions to this problem. Thanks everyone!

**EDIT: adding in the /project/edit.html.erb code and PATCH request for clarification.**
Edit view:

<% provide(:title, "Edit project") %>

Update your project status

<%= minimal_form_for @project, html: { class: "form-inline"} do |f| %>
<% if @project.errors.any? %>
<%= render 'shared/error_messages', object: f.object %>
<% end %>

Choose an existing task

<%= hidden_field_tag "assignment[][task_id]", nil %>
<%= f.association :tasks, :collection => Task.all.to_a, :label_method => :item,
:as => :check_boxes,
:wrapper => :vertical_radio_and_checkboxes,
:checked => params[:task_id] %>
<%= render 'form', f: f %>
<%= f.submit "Save changes", class: "btn btn-primary" %>
<% end %>

PATCH request when unchecking one of the tasks:

"project"=>{"task_ids"=>["63", "53", ""],
"tasks_attributes"=>{"0"=>{"item"=>"andadd", "description"=>"addmore", "complexity"=>"low",
"est_time"=>"1", "actual_time"=>"3", "_destroy"=>"false", "id"=>"63"},
"1"=>{"item"=>"independent", "description"=>"newtask", "complexity"=>"low",
"est_time"=>"2.5", "actual_time"=>"3.5", "_destroy"=>"false", "id"=>"53"},
"2"=>{"item"=>"TESTER", "description"=>"TESTEE", "complexity"=>"low",
"est_time"=>"3", "actual_time"=>"11", "_destroy"=>"1", "id"=>"28"}}},
"commit"=>"Save changes",

1 thought on “How do I remove a has_many through association properly?”

  1. You need to set the inverse_of property on your relationships.

    edit: quick explanation

    > I believe I’m getting this error because AR is looking for a Task that has already been disassociated from the Project, but that’s just a hunch. Thoughts?

    This was the right track, because the inverse_of wasn’t set, the task_attributes were not being associated with the parent object. With inverse_of set, you’re able to refer the parent object in reverse without re-initializing it.


Leave a Comment