# frozen_string_literal: true module Decidim # Adapted from https://hashrocket.com/blog/posts/recursive-sql-in-activerecord module ActsAsTree extend ActiveSupport::Concern included do @parent_item_foreign_key = :parent_id end class_methods do def parent_item_foreign_key(name = nil) return @parent_item_foreign_key unless name @parent_item_foreign_key = name end def parent_item_polymorphic_type_key(name = nil) return @parent_item_polymorphic_type_key unless name @parent_item_polymorphic_type_key = name end def tree_for(item) where(Arel.sql("#{table_name}.id IN (#{tree_sql_for(item)})")).order("#{table_name}.id") end def polymorphic_condition(item) return "" if parent_item_polymorphic_type_key.blank? "AND #{table_name}.#{parent_item_polymorphic_type_key} = '#{item.class.name}'" end def tree_sql_for(item) <<-SQL.squish WITH RECURSIVE search_tree(id, path) AS ( SELECT id, ARRAY[id] FROM #{table_name} WHERE id = #{item.id} UNION ALL SELECT #{table_name}.id, path || #{table_name}.id FROM search_tree JOIN #{table_name} ON #{table_name}.#{parent_item_foreign_key} = search_tree.id #{polymorphic_condition(item)} WHERE NOT #{table_name}.id = ANY(path) ) SELECT id FROM search_tree ORDER BY path SQL end end def descendants @descendants ||= self_and_descendants.where.not(id:) end def self_and_descendants @self_and_descendants ||= self.class.tree_for(self) end end end