使用自参考记录防止无限循环

时间:2015-08-20 13:13:41

标签: ruby-on-rails-3 postgresql join

我有一个通过连接表进行自我参照连接的模型,定义如下:

class Task < ActiveRecord::Base
    has_many :dependency_dependents, foreign_key: :dependency_id, class_name: 'TaskDependency', dependent: :destroy
    has_many :dependency_dependencies, foreign_key: :task_id, class_name: 'TaskDependency', dependent: :destroy, autosave: true

    has_many :dependencies, through: :dependency_dependencies
    has_many :dependents, through: :dependency_dependents, source: :task
end

class TaskDependency < ActiveRecord::Base
    belongs_to :task
    belongs_to :dependency, class_name: 'Task'
end

连接的所有内容都很有效。保存记录后,它会根据所依赖的记录执行一系列计算,然后继续更新依赖记录,从而对这些记录执行相同的计算等等。

问题在于,如果依赖任务链中的某个地方存在对树上其他地方的任务的依赖,首先计算将失败,但最重要的是,它会导致无限循环的计算和更新。

在我保存记录之前,有没有一种很好的方法可以检查这个无限循环(理想情况下,首先在创建依赖项之前)。

我很高兴使用纯SQL或ruby来做这件事,只是想知道是否有人为此提供了一个干净的解决方案。

1 个答案:

答案 0 :(得分:0)

是的,我想我已经明白了。当记录验证时,它会调用一个新的验证方法,该方法运行一个递归查询,该查询将其下面的层次结构中的所有ID拉出,并且如果找到了您尝试建立依赖关系的任务的ID,依赖者列表,然后验证失败。对此的查询如下:

WITH RECURSIVE dependencies(task_id, path) AS (
  SELECT task_id, ARRAY[task_id]
  FROM task_dependencies
  WHERE dependency_id = #{self.id}

  UNION ALL

  SELECT task_dependencies.task_id, path || task_dependencies.task_id
  FROM dependencies
  JOIN task_dependencies ON dependencies.task_id = task_dependencies.dependency_id
  WHERE NOT task_dependencies.task_id = ANY(path)

)

SELECT task_id id FROM dependencies ORDER BY path

永远都不知道,也许这会帮助一路上的人。 正如@ craig-ringer所提到的那样,仍有可能出现两个并发插入导致竞争条件,但是这个查询也可以依次锁定表以防止在必要时发生这种情况。