更新collection_singular_ids时如何触发`dependent:destroy`

时间:2017-08-31 16:54:13

标签: mysql ruby-on-rails ruby ruby-on-rails-4

考虑以下设置:

class Task
  has_many :users, through: :task_users
end

class User
  has_many :tasks, through: :tasks_users
end

class TaskUser
  # Joins table
  has_many :comments, dependent: :destroy
end    

class Comment
  belongs_to :task_user
end

现在,如果我执行标准#destroy命令,例如:

tu = TaskUser.first
tu.destroy

然后,与任务用户相关的所有评论也将被销毁。

但是,假设您想通过#collection_singular_ids=更新用户的任务,如下所示:

u = User.first
puts u.task_ids # => [1, 2, 3]
u.task_ids = [1, 2]

执行此操作(甚至不显式调用#save!)将触发SQL,如:

(0.3ms)  BEGIN
  SQL (0.4ms)  DELETE FROM `task_users` WHERE `task_users`.`task_id` = 3 AND `task_users`.`user_id` = 1 
(2.0ms)  COMMIT

...因此,关联的Comment成为孤儿。

如果您使用#attributes=

,则会出现同样的问题
u.attributes = { task_ids: [1, 2] }

是否有一种干净的方法可以确保相关的Comment将永远被销毁(即永远不会成为孤儿)?

1 个答案:

答案 0 :(得分:0)

感谢@engineersmnky指出我正确的方向。

这可能不是最漂亮的解决方案,但可行的选择是define a callback on the association,例如:

class User
  has_many :tasks,
    through: :tasks_users,
    before_remove: ->(user, task) do
      task_user = TaskUser.find_by!(user: user, task: task)
      task_user.comments.each(&:destroy!)
    end

  # Or alternatively, this can be defined as a method:

  has_many :tasks,
    through: :tasks_users,
    before_remove: :destroy_task_user_comments

  private

  def destroy_task_user_comments(task)
    task_user = TaskUser.find_by!(user: self, task: task)
    task_user.comments.each(&:destroy!)
  end
end

请注意,我在块中使用了bang(!)方法,因此如果引发异常,整个事务将回滚 - 根据文档:

  

如果任何before_remove回调抛出异常,则不会从集合中删除该对象。