Module: ClosureTree::Finders::ClassMethods

Defined in:
lib/closure_tree/finders.rb

Instance Method Summary collapse

Instance Method Details

#find_all_by_generation(generation_level) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/closure_tree/finders.rb', line 115

def find_all_by_generation(generation_level)
  s = joins(<<-SQL.strip_heredoc)
    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) ⇒ Object

Find the node whose ancestry_path is path



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# 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.strip_heredoc)
      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 = {}) ⇒ Object

Find or create nodes such that the ancestry_path is path



158
159
160
161
162
163
164
165
166
167
168
169
# 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

#leavesObject



72
73
74
75
76
77
78
79
80
81
82
# File 'lib/closure_tree/finders.rb', line 72

def leaves
  s = joins(<<-SQL.strip_heredoc)
    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) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
# 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

#rootObject

Returns an arbitrary node that has no parents.



68
69
70
# File 'lib/closure_tree/finders.rb', line 68

def root
  roots.first
end

#rootsObject



63
64
65
# File 'lib/closure_tree/finders.rb', line 63

def roots
  _ct.scope_with_order(where(_ct.parent_column_name => nil))
end

#with_ancestor(*ancestors) ⇒ Object



84
85
86
87
88
89
90
91
# 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) ⇒ Object



93
94
95
96
97
98
99
100
# 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) ⇒ Object



55
56
57
58
59
60
61
# 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