0

I have 3 models as follows:

class Document < ActiveRecord::Base
  has_many :documents_tasks, inverse_of: :document
  has_many :tasks, through: :documents_tasks, dependent: :destroy
end

class Task < ActiveRecord::Base
  has_many :documents_tasks, inverse_of: :task
  has_many :documents, through: :documents_tasks, dependent: :destroy
end

class DocumentsTask < ActiveRecord::Base
  belongs_to :task, inverse_of: :documents_tasks
  belongs_to :document, inverse_of: :documents_tasks

  validates_uniqueness_of :document_id, scope: :task_id
end

In the above when I try to update the record for a Task it throws a validation error for duplicate entries on the DocumentsTask model if I keep the validation or directly inserts duplicates if remove the validation.

My code to update the Task record is:

def update
  @task = @coach.tasks.find(params[:id])
  @task.update(:name => task_params[:name], :description => task_params[:description] )
  @task.documents << Document.find(task_params[:documents])
  if @task.save
    render 'show'
  else
    render status: 500, json: {
        error: true,
        reason: @task.errors.full_messages.to_sentence
    }
  end
end

I know I can add unique index to the db to automatically prevent duplicate entries but is there some way I can prevent the controller from updating the join table values when they're the same?

So when I attempt to update the associated documents, ex:

  • I had document 5 initially
  • Now I add document 6 and call the update function

It attempts to re-add both documents 5 and 6 to the db so I get the error:

Completed 422 Unprocessable Entity in 9176ms

ActiveRecord::RecordInvalid (Validation failed: Document has already been taken)

This is because I added the following validation:

validates_uniqueness_of :document_id, scope: :task_id

in my DocumentsTask model as shown above. The issue is how can I prevent it from attempting to re-add existing records

anonn023432
  • 2,940
  • 6
  • 31
  • 63
  • Are you able to post the specific error you're getting or the logs for the update request? On first glance, you may be fine if you just replace `@task.update` with `@task.assign_attributes` as right now you're calling update and then save right after (which will persist data twice so has the potential for duplicates). But can't say for sure without seeing a little more detail. – Matt Mar 21 '18 at 19:58
  • @Matt it throws the general `model validation` error because it is attempting to re-insert existing values in the join table. The issue is caused by the fact that it attempts to insert the existing values in to the join table because they get passed to it as a part of the params – anonn023432 Mar 21 '18 at 20:01
  • Not totallyyyy following (sorry :S). Are you able to post a log for the update request? – Matt Mar 21 '18 at 20:03
  • @Matt I edited my question to add more information – anonn023432 Mar 21 '18 at 20:14
  • Thanks, I made a couple of assumptions and posted an answer. Hope it helps! – Matt Mar 21 '18 at 23:03

1 Answers1

1

Assuming that task_params[:documents] is an array of document ids (based on how you're using it with find now), you should be able to do something like this as a quick fix:

@task.documents << Document.where(id: task_params[:documents]).where.not(task_id: @task.id)

Basically this just filters out the documents that are already associated to the given task before assigning them to the task.

That said, I'd suggest something more robust as a long term solution. A couple of options (among many) would be extracting the responsibility of task creation out into it's own class (so you can more easily test it and make that functionality more portable), or you could look into overriding the setter method(s) for documents in your task model similar to what this answer describes: https://stackoverflow.com/a/2891245/456673

Matt
  • 337
  • 3
  • 15