我最终放弃了 awesome_nested_set gem,然后浏览了这个优秀的博客:
http://hashrocket.com/blog/posts/recursive-sql-in-activerecord
然后我修改了类方法以接受一个对象数组,提取到一个模块并添加了一些方法,如下所示:
module RecursiveTree
def self.included(recipient)
recipient.extend(ModelClassMethods)
end
module ModelClassMethods
def tree_for_ids(methods)
self.where("#{table_name}.id IN (#{self.tree_sql_for_ids(self.sql_clean(methods))})")
end
def tree_for_id(id)
self.where("#{table_name}.id IN (#{self.tree_sql_for_ids("id = #{id}")})")
end
def sql_clean(methods)
sql_to_exec = ""
methods.each do |method|
sql_string = method.to_sql #note we can pass methods and they will not get executed unless we chain something to them - because of lazy instatiation
#remove everything after the "ORDER" string as can't order more than once and order doesnt matter anyway
sql_string = sql_string.slice(0..(sql_string.index('ORDER')-2)) unless sql_string.index('ORDER').nil?
#add an OR to the front if there is already some text in the sql_to_exec
sql_to_exec += "#{" OR " unless sql_to_exec.empty?}#{table_name}.id IN (#{sql_string})"
end
sql_to_exec.gsub! '*', 'id' #sub to only return the ids
end
def tree_sql_for_ids(sql_id_array)
tree_sql = sql_id_array ? <<-SQL
WITH RECURSIVE search_tree(id, path) AS (
SELECT id, ARRAY[id]
FROM #{table_name}
WHERE #{sql_id_array}
UNION ALL
SELECT #{table_name}.id, path || #{table_name}.id
FROM search_tree
JOIN #{table_name} ON #{table_name}.parent_id = search_tree.id
WHERE NOT #{table_name}.id = ANY(path)
)
SELECT id FROM search_tree ORDER BY path
SQL
: "NULL"
end
def ancestors_sql_for(instance)
ancestors_sql = instance.id ? <<-SQL
WITH RECURSIVE search_tree(id, path) AS (
SELECT parent_id, ARRAY[parent_id]
FROM #{table_name}
WHERE id = #{instance.id}
UNION ALL
SELECT #{table_name}.parent_id, path || #{table_name}.parent_id
FROM search_tree
JOIN #{table_name} ON #{table_name}.id = search_tree.id
WHERE NOT (#{table_name}.parent_id IS NULL)
)
SELECT id FROM search_tree ORDER BY path
SQL
: "NULL"
end
end
#instance methods go here:
def ancestors
self.class.where("#{self.class.table_name}.id IN (#{self.class.ancestors_sql_for(self)})").order("#{self.class.table_name}.id")
end
def ancestor_complete
ancestors.where(complete: true).any?
end
def top_level_parent
is_top_level_parent? ? self : self.ancestors.where(parent_id: nil).first
end
def is_top_level_parent?
self.parent.nil?
end
def descendants
self.id ? self_and_descendants.where.not(id: self.id) : self.class.none #self.class.none return an empty ActiveRecord relation
end
def self_and_descendants
self.id ? self.class.tree_for_id(self.id) : self.class.none #self.class.none return an empty ActiveRecord relation
end
end
然后包含在我的任务模型中:
class Task < ActiveRecord::Base
include RecursiveTree #in the lib folder
...
end
十个我可以用任意数量的方法调用,例如:
u1 = User.find(1)
Task.tree_for_ids([u1.tasks_shared_to, u1.tasks_shared_to_through_groups])
只需 1 个查询即可完成工作!