【问题标题】:Updating record with join table creates duplicate entries使用连接表更新记录会创建重复条目
【发布时间】:2018-03-21 18:48:22
【问题描述】:

我有3个模型如下:

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

在上面,当我尝试更新 Task 的记录时,如果我保留验证,则会对 DocumentsTask 模型上的重复条目引发验证错误,或者如果删除验证则直接插入重复项。

我更新任务记录的代码是:

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

我知道我可以将unique index 添加到数据库以自动防止重复条目,但是有什么方法可以防止controller 在它们相同时更新连接表值?

所以当我尝试更新相关文档时,例如:

  • 我最初有文档 5
  • 现在我添加文档 6 并调用更新函数

它尝试将文档 5 和 6 重新添加到数据库中,所以我得到了错误:

Completed 422 Unprocessable Entity in 9176ms

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

这是因为我添加了以下验证:

validates_uniqueness_of :document_id, scope: :task_id

在我的 DocumentsTask 模型中,如上所示。问题是如何防止它尝试重新添加现有记录

【问题讨论】:

  • 您能否发布您遇到的具体错误或更新请求的日志?乍一看,如果您只是将@task.update 替换为@task.assign_attributes,您可能会很好,因为您现在正在调用更新,然后立即保存(这将保存数据两次,因此有可能重复)。但是如果不看更多细节就不能肯定地说。
  • @Matt 它会抛出一般的model validation 错误,因为它试图在连接表中重新插入现有值。问题是由于它尝试将现有值插入到连接表中,因为它们作为参数的一部分传递给它
  • 不完全关注(对不起:S)。您可以发布更新请求的日志吗?
  • @Matt 我编辑了我的问题以添加更多信息
  • 谢谢,我做了几个假设并发布了答案。希望对您有所帮助!

标签: ruby-on-rails ruby-on-rails-4 join activerecord


【解决方案1】:

假设 task_params[:documents] 是一个文档 ID 数组(基于您现在如何将它与 find 一起使用),您应该能够执行以下操作作为快速修复:

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

基本上,这只是在将已与给定任务关联的文档分配给任务之前过滤掉它们。

也就是说,我建议将一些更强大的东西作为长期解决方案。有几个选项(在许多选项中)会将创建任务的责任提取到它自己的类中(这样您就可以更轻松地测试它并使该功能更具可移植性),或者您可以考虑覆盖 setter 方法您的任务模型中的文档类似于此答案描述的内容:https://stackoverflow.com/a/2891245/456673

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-03
    • 1970-01-01
    • 2021-01-11
    • 1970-01-01
    • 2013-12-11
    • 1970-01-01
    相关资源
    最近更新 更多