使用自引用记录防止无限循环
Prevent infinite loop with self-referencial records
我有一个模型,它通过连接 table 进行自引用连接,定义如下:
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 来做到这一点,只是想知道是否有人对此有一个干净的解决方案。
好的,我想我已经弄明白了。当记录验证时,它会调用一个新的验证方法,该方法运行一个递归查询,该方法会提取其下方层次结构中的所有 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 所提到的,仍然有可能发生两个并发插入导致竞争条件,但是这个查询也可以反过来锁定 table 以防止在必要时发生这种情况。
我有一个模型,它通过连接 table 进行自引用连接,定义如下:
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 来做到这一点,只是想知道是否有人对此有一个干净的解决方案。
好的,我想我已经弄明白了。当记录验证时,它会调用一个新的验证方法,该方法运行一个递归查询,该方法会提取其下方层次结构中的所有 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 所提到的,仍然有可能发生两个并发插入导致竞争条件,但是这个查询也可以反过来锁定 table 以防止在必要时发生这种情况。