module ClosureTree::Finders::ClassMethods

Public Instance Methods

find_all_by_generation(generation_level) click to toggle source
# File lib/closure_tree/finders.rb, line 115
      def find_all_by_generation(generation_level)
        s = joins(<<-SQL.squish)
          INNER JOIN (
            SELECT #{primary_key} as root_id
            FROM #{_ct.quoted_table_name}
            WHERE #{_ct.quoted_parent_column_name} IS NULL
          ) #{ _ct.t_alias_keyword }  roots ON (1 = 1)
          INNER JOIN (
            SELECT ancestor_id, descendant_id
            FROM #{_ct.quoted_hierarchy_table_name}
            GROUP BY ancestor_id, descendant_id
            HAVING MAX(generations) = #{generation_level.to_i}
          ) #{ _ct.t_alias_keyword }  descendants ON (
            #{_ct.quoted_table_name}.#{primary_key} = descendants.descendant_id
            AND roots.root_id = descendants.ancestor_id
          )
        SQL
        _ct.scope_with_order(s)
      end
find_by_path(path, attributes = {}, parent_id = nil) click to toggle source

Find the node whose ancestry_path is path

# File lib/closure_tree/finders.rb, line 136
      def find_by_path(path, attributes = {}, parent_id = nil)
        return nil if path.blank?
        path = _ct.build_ancestry_attr_path(path, attributes)
        if path.size > _ct.max_join_tables
          return _ct.find_by_large_path(path, attributes, parent_id)
        end
        scope = where(path.pop)
        last_joined_table = _ct.table_name
        path.reverse.each_with_index do |ea, idx|
          next_joined_table = "p#{idx}"
          scope = scope.joins(<<-SQL.squish)
            INNER JOIN #{_ct.quoted_table_name} #{ _ct.t_alias_keyword } #{next_joined_table}
              ON #{next_joined_table}.#{_ct.quoted_id_column_name} =
 #{connection.quote_table_name(last_joined_table)}.#{_ct.quoted_parent_column_name}
          SQL
          scope = _ct.scoped_attributes(scope, ea, next_joined_table)
          last_joined_table = next_joined_table
        end
        scope.where("#{last_joined_table}.#{_ct.parent_column_name}" => parent_id).readonly(false).first
      end
find_or_create_by_path(path, attributes = {}) click to toggle source

Find or create nodes such that the ancestry_path is path

# File lib/closure_tree/finders.rb, line 158
def find_or_create_by_path(path, attributes = {})
  attr_path = _ct.build_ancestry_attr_path(path, attributes)
  find_by_path(attr_path) || begin
    root_attrs = attr_path.shift
    _ct.with_advisory_lock do
      # shenanigans because find_or_create can't infer that we want the same class as this:
      # Note that roots will already be constrained to this subclass (in the case of polymorphism):
      root = roots.where(root_attrs).first || _ct.create!(self, root_attrs)
      root.find_or_create_by_path(attr_path)
    end
  end
end
leaves() click to toggle source
# File lib/closure_tree/finders.rb, line 72
      def leaves
        s = joins(<<-SQL.squish)
          INNER JOIN (
            SELECT ancestor_id
            FROM #{_ct.quoted_hierarchy_table_name}
            GROUP BY ancestor_id
            HAVING MAX(#{_ct.quoted_hierarchy_table_name}.generations) = 0
          ) #{ _ct.t_alias_keyword } leaves ON (#{_ct.quoted_table_name}.#{primary_key} = leaves.ancestor_id)
        SQL
        _ct.scope_with_order(s.readonly(false))
      end
lowest_common_ancestor(*descendants) click to toggle source
# File lib/closure_tree/finders.rb, line 102
def lowest_common_ancestor(*descendants)
  descendants = descendants.first if descendants.length == 1 && descendants.first.respond_to?(:each)
  ancestor_id = hierarchy_class
    .where(descendant_id: descendants)
    .group(:ancestor_id)
    .having("COUNT(ancestor_id) = #{descendants.count}")
    .order(Arel.sql('MIN(generations) ASC'))
    .limit(1)
    .pluck(:ancestor_id).first

  find_by(primary_key => ancestor_id) if ancestor_id
end
root() click to toggle source

Returns an arbitrary node that has no parents.

# File lib/closure_tree/finders.rb, line 68
def root
  roots.first
end
roots() click to toggle source
# File lib/closure_tree/finders.rb, line 63
def roots
  _ct.scope_with_order(where(_ct.parent_column_name => nil))
end
with_ancestor(*ancestors) click to toggle source
# File lib/closure_tree/finders.rb, line 84
def with_ancestor(*ancestors)
  ancestor_ids = ancestors.map { |ea| ea.is_a?(ActiveRecord::Base) ? ea._ct_id : ea }
  scope = ancestor_ids.blank? ? all : joins(:ancestor_hierarchies).
    where("#{_ct.hierarchy_table_name}.ancestor_id" => ancestor_ids).
    where("#{_ct.hierarchy_table_name}.generations > 0").
    readonly(false)
  _ct.scope_with_order(scope)
end
with_descendant(*descendants) click to toggle source
# File lib/closure_tree/finders.rb, line 93
def with_descendant(*descendants)
  descendant_ids = descendants.map { |ea| ea.is_a?(ActiveRecord::Base) ? ea._ct_id : ea }
  scope = descendant_ids.blank? ? all : joins(:descendant_hierarchies).
    where("#{_ct.hierarchy_table_name}.descendant_id" => descendant_ids).
    where("#{_ct.hierarchy_table_name}.generations > 0").
    readonly(false)
  _ct.scope_with_order(scope)
end
without_instance(instance) click to toggle source
# File lib/closure_tree/finders.rb, line 55
def without_instance(instance)
  if instance.new_record?
    all
  else
    where(["#{_ct.quoted_table_name}.#{_ct.quoted_id_column_name} != ?", instance.id])
  end
end