我正在使用awesome_nested_set gem并尝试获取has_many关系的所有后代。用户有许多共享任务,每个任务都有很多孩子。我可以获得一个用户task_shared_to和他们所有孩子的列表(注意孩子不会单独分享给用户)?
用户模型:
has_many :tasks_shared_to, through: :user_task_shares, source: :task
任务模型:
acts_as_nested_set
belongs_to :created_by, :class_name => 'User', :foreign_key => 'created_by_id'
belongs_to :parent, :class_name => 'Task', :foreign_key => 'parent_id'
我知道通过每个tasks_shared_to循环然后调用self_and_descendants
会很容易,但我希望能够在每个循环中不执行单独的SQL调用的情况下执行此操作。
答案 0 :(得分:0)
我最终抛弃了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])
这只能在一次查询中完成工作!