module Ancestry::InstanceMethods

Public Instance Methods

ancestor_conditions() click to toggle source
# File lib/ancestry/instance_methods.rb, line 110
def ancestor_conditions
  t = get_arel_table
  t[get_primary_key_column].in(ancestor_ids)
end
ancestor_ids() click to toggle source
# File lib/ancestry/instance_methods.rb, line 106
def ancestor_ids
  parse_ancestry_column(read_attribute(self.ancestry_base_class.ancestry_column))
end
ancestor_ids_was() click to toggle source
# File lib/ancestry/instance_methods.rb, line 123
def ancestor_ids_was
  parse_ancestry_column(changed_attributes[self.ancestry_base_class.ancestry_column.to_s])
end
ancestor_of?(node) click to toggle source
# File lib/ancestry/instance_methods.rb, line 148
def ancestor_of?(node)
  node.ancestor_ids.include?(self.id)
end
ancestor_was_conditions() click to toggle source
# File lib/ancestry/instance_methods.rb, line 119
def ancestor_was_conditions
  {primary_key_with_table => ancestor_ids_was}
end
ancestors(depth_options = {}) click to toggle source
# File lib/ancestry/instance_methods.rb, line 115
def ancestors depth_options = {}
  self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.where ancestor_conditions
end
ancestry_callbacks_disabled?() click to toggle source
# File lib/ancestry/instance_methods.rb, line 302
def ancestry_callbacks_disabled?
  defined?(@disable_ancestry_callbacks) && @disable_ancestry_callbacks
end
ancestry_changed?() click to toggle source

Ancestors

# File lib/ancestry/instance_methods.rb, line 98
def ancestry_changed?
  changed.include?(self.ancestry_base_class.ancestry_column.to_s)
end
ancestry_exclude_self() click to toggle source

Validate that the ancestors don't include itself

# File lib/ancestry/instance_methods.rb, line 4
def ancestry_exclude_self
  errors.add(:base, "#{self.class.name.humanize} cannot be a descendant of itself.") if ancestor_ids.include? self.id
end
apply_orphan_strategy() click to toggle source

Apply orphan strategy

# File lib/ancestry/instance_methods.rb, line 32
def apply_orphan_strategy
  # Skip this if callbacks are disabled
  unless ancestry_callbacks_disabled?
    # If this isn't a new record ...
    unless new_record?
      # ... make all children root if orphan strategy is rootify
      if self.ancestry_base_class.orphan_strategy == :rootify
        unscoped_descendants.each do |descendant|
          descendant.without_ancestry_callbacks do
            descendant.update_attribute descendant.class.ancestry_column, (if descendant.ancestry == child_ancestry then nil else descendant.ancestry.gsub(/^#{child_ancestry}\//, '') end)
          end
        end
      # ... destroy all descendants if orphan strategy is destroy
      elsif self.ancestry_base_class.orphan_strategy == :destroy
        unscoped_descendants.each do |descendant|
          descendant.without_ancestry_callbacks do
            descendant.destroy
          end
        end
      # ... make child elements of this node, child of its parent if orphan strategy is adopt
      elsif self.ancestry_base_class.orphan_strategy == :adopt
        descendants.each do |descendant|
          descendant.without_ancestry_callbacks do
            new_ancestry = descendant.ancestor_ids.delete_if { |x| x == self.id }.join("/")
            # check for empty string if it's then set to nil
            new_ancestry = nil if new_ancestry.empty?
            descendant.update_attribute descendant.class.ancestry_column, new_ancestry || nil
          end
        end
      # ... throw an exception if it has children and orphan strategy is restrict
      elsif self.ancestry_base_class.orphan_strategy == :restrict
        raise Ancestry::AncestryException.new('Cannot delete record because it has descendants.') unless is_childless?
      end
    end
  end
end
cache_depth() click to toggle source
# File lib/ancestry/instance_methods.rb, line 144
def cache_depth
  write_attribute self.ancestry_base_class.depth_cache_column, depth
end
child_ancestry() click to toggle source

The ancestry value for this record's children

# File lib/ancestry/instance_methods.rb, line 89
def child_ancestry
  # New records cannot have children
  raise Ancestry::AncestryException.new('No child ancestry for new record. Save record before performing tree operations.') if new_record?

  if self.send("#{self.ancestry_base_class.ancestry_column}_was").blank? then id.to_s else "#{self.send "#{self.ancestry_base_class.ancestry_column}_was"}/#{id}" end
end
child_conditions() click to toggle source

Children

# File lib/ancestry/instance_methods.rb, line 199
def child_conditions
  t = get_arel_table
  t[get_ancestry_column].eq(child_ancestry)
end
child_ids() click to toggle source
# File lib/ancestry/instance_methods.rb, line 208
def child_ids
  children.select(self.ancestry_base_class.primary_key).map(&self.ancestry_base_class.primary_key.to_sym)
end
child_of?(node) click to toggle source
# File lib/ancestry/instance_methods.rb, line 222
def child_of?(node)
  self.parent_id == node.id
end
childless?()
Alias for: is_childless?
children() click to toggle source
# File lib/ancestry/instance_methods.rb, line 204
def children
  self.ancestry_base_class.where child_conditions
end
children?()
Alias for: has_children?
depth() click to toggle source
# File lib/ancestry/instance_methods.rb, line 140
def depth
  ancestor_ids.size
end
descendant_conditions() click to toggle source

Descendants

# File lib/ancestry/instance_methods.rb, line 257
def descendant_conditions
  t = get_arel_table
  # rails has case sensitive matching.
  if defined?(ActiveRecord.version) && ActiveRecord.version.to_s >= "5"
    t[get_ancestry_column].matches("#{child_ancestry}/%", nil, true).or(t[get_ancestry_column].eq(child_ancestry))
  else
    t[get_ancestry_column].matches("#{child_ancestry}/%").or(t[get_ancestry_column].eq(child_ancestry))
  end
end
descendant_ids(depth_options = {}) click to toggle source
# File lib/ancestry/instance_methods.rb, line 271
def descendant_ids depth_options = {}
  descendants(depth_options).select(self.ancestry_base_class.primary_key).collect(&self.ancestry_base_class.primary_key.to_sym)
end
descendant_of?(node) click to toggle source
# File lib/ancestry/instance_methods.rb, line 275
def descendant_of?(node)
  ancestor_ids.include?(node.id)
end
descendants(depth_options = {}) click to toggle source
# File lib/ancestry/instance_methods.rb, line 267
def descendants depth_options = {}
  self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).where descendant_conditions
end
has_children?() click to toggle source
# File lib/ancestry/instance_methods.rb, line 212
def has_children?
  self.children.exists?({})
end
Also aliased as: children?
has_siblings?() click to toggle source
# File lib/ancestry/instance_methods.rb, line 241
def has_siblings?
  self.siblings.count > 1
end
Also aliased as: siblings?
is_childless?() click to toggle source
# File lib/ancestry/instance_methods.rb, line 217
def is_childless?
  !has_children?
end
Also aliased as: childless?
is_only_child?() click to toggle source
# File lib/ancestry/instance_methods.rb, line 246
def is_only_child?
  !has_siblings?
end
Also aliased as: only_child?
is_root?() click to toggle source
# File lib/ancestry/instance_methods.rb, line 188
def is_root?
  read_attribute(self.ancestry_base_class.ancestry_column).blank?
end
Also aliased as: root?
only_child?()
Alias for: is_only_child?
parent() click to toggle source
# File lib/ancestry/instance_methods.rb, line 166
def parent
  if parent_id.blank? then nil else unscoped_find(parent_id) end
end
parent=(parent) click to toggle source

Parent

# File lib/ancestry/instance_methods.rb, line 154
def parent= parent
  write_attribute(self.ancestry_base_class.ancestry_column, if parent.nil? then nil else parent.child_ancestry end)
end
parent_id() click to toggle source
# File lib/ancestry/instance_methods.rb, line 162
def parent_id
  if ancestor_ids.empty? then nil else ancestor_ids.last end
end
parent_id=(parent_id) click to toggle source
# File lib/ancestry/instance_methods.rb, line 158
def parent_id= parent_id
  self.parent = if parent_id.blank? then nil else unscoped_find(parent_id) end
end
parent_id?() click to toggle source
# File lib/ancestry/instance_methods.rb, line 170
def parent_id?
  parent_id.present?
end
parent_of?(node) click to toggle source
# File lib/ancestry/instance_methods.rb, line 174
def parent_of?(node)
  self.id == node.parent_id
end
parse_ancestry_column(obj) click to toggle source
# File lib/ancestry/instance_methods.rb, line 102
def parse_ancestry_column obj
  obj.to_s.split('/').map { |id| cast_primary_key(id) }
end
path(depth_options = {}) click to toggle source
# File lib/ancestry/instance_methods.rb, line 136
def path depth_options = {}
  self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.where path_conditions
end
path_conditions() click to toggle source
# File lib/ancestry/instance_methods.rb, line 131
def path_conditions
  t = get_arel_table
  t[get_primary_key_column].in(path_ids)
end
path_ids() click to toggle source
# File lib/ancestry/instance_methods.rb, line 127
def path_ids
  ancestor_ids + [id]
end
root() click to toggle source
# File lib/ancestry/instance_methods.rb, line 184
def root
  if root_id == id then self else unscoped_find(root_id) end
end
root?()
Alias for: is_root?
root_id() click to toggle source

Root

# File lib/ancestry/instance_methods.rb, line 180
def root_id
  if ancestor_ids.empty? then id else ancestor_ids.first end
end
root_of?(node) click to toggle source
# File lib/ancestry/instance_methods.rb, line 193
def root_of?(node)
  self.id == node.root_id
end
sibling_conditions() click to toggle source

Siblings

# File lib/ancestry/instance_methods.rb, line 228
def sibling_conditions
  t = get_arel_table
  t[get_ancestry_column].eq(read_attribute(self.ancestry_base_class.ancestry_column))
end
sibling_ids() click to toggle source
# File lib/ancestry/instance_methods.rb, line 237
def sibling_ids
  siblings.select(self.ancestry_base_class.primary_key).collect(&self.ancestry_base_class.primary_key.to_sym)
end
sibling_of?(node) click to toggle source
# File lib/ancestry/instance_methods.rb, line 251
def sibling_of?(node)
  self.ancestry == node.ancestry
end
siblings() click to toggle source
# File lib/ancestry/instance_methods.rb, line 233
def siblings
  self.ancestry_base_class.where sibling_conditions
end
siblings?()
Alias for: has_siblings?
subtree(depth_options = {}) click to toggle source
# File lib/ancestry/instance_methods.rb, line 286
def subtree depth_options = {}
  self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).where subtree_conditions
end
subtree_conditions() click to toggle source

Subtree

# File lib/ancestry/instance_methods.rb, line 281
def subtree_conditions
  t = get_arel_table
  descendant_conditions.or(t[get_primary_key_column].eq(self.id))
end
subtree_ids(depth_options = {}) click to toggle source
# File lib/ancestry/instance_methods.rb, line 290
def subtree_ids depth_options = {}
  subtree(depth_options).select(self.ancestry_base_class.primary_key).collect(&self.ancestry_base_class.primary_key.to_sym)
end
touch_ancestors_callback() click to toggle source

Touch each of this record's ancestors

# File lib/ancestry/instance_methods.rb, line 70
def touch_ancestors_callback

  # Skip this if callbacks are disabled
  unless ancestry_callbacks_disabled?

    # Only touch if the option is enabled
    if self.ancestry_base_class.touch_ancestors

      # Touch each of the old *and* new ancestors
      self.class.where(id: (ancestor_ids + ancestor_ids_was).uniq).each do |ancestor|
        ancestor.without_ancestry_callbacks do
          ancestor.touch
        end
      end
    end
  end
end
update_descendants_with_new_ancestry() click to toggle source

Update descendants with new ancestry

# File lib/ancestry/instance_methods.rb, line 9
def update_descendants_with_new_ancestry
  # Skip this if callbacks are disabled
  unless ancestry_callbacks_disabled?
    # If node is not a new record and ancestry was updated and the new ancestry is sane ...
    if ancestry_changed? && !new_record? && sane_ancestry?
      # ... for each descendant ...
      unscoped_descendants.each do |descendant|
        # ... replace old ancestry with new ancestry
        descendant.without_ancestry_callbacks do
          descendant.update_attribute(
            self.ancestry_base_class.ancestry_column,
            descendant.read_attribute(descendant.class.ancestry_column).gsub(
              /^#{self.child_ancestry}/,
              if read_attribute(self.class.ancestry_column).blank? then id.to_s else "#{read_attribute self.class.ancestry_column }/#{id}" end
            )
          )
        end
      end
    end
  end
end
without_ancestry_callbacks() { || ... } click to toggle source

Callback disabling

# File lib/ancestry/instance_methods.rb, line 296
def without_ancestry_callbacks
  @disable_ancestry_callbacks = true
  yield
  @disable_ancestry_callbacks = false
end

Private Instance Methods

cast_primary_key(key) click to toggle source
# File lib/ancestry/instance_methods.rb, line 308
def cast_primary_key(key)
  if [:string, :uuid, :text].include? primary_key_type
    key
  else
    key.to_i
  end
end
get_ancestry_column() click to toggle source
# File lib/ancestry/instance_methods.rb, line 344
def get_ancestry_column
  self.ancestry_base_class.ancestry_column.to_sym
end
get_arel_table() click to toggle source
# File lib/ancestry/instance_methods.rb, line 336
def get_arel_table
  self.ancestry_base_class.arel_table
end
get_primary_key_column() click to toggle source
# File lib/ancestry/instance_methods.rb, line 340
def get_primary_key_column
  self.ancestry_base_class.primary_key.to_sym
end
primary_key_type() click to toggle source
# File lib/ancestry/instance_methods.rb, line 316
def primary_key_type
  @primary_key_type ||= column_for_attribute(self.class.primary_key).type
end
sane_ancestry?() click to toggle source

Validates the ancestry, but can also be applied if validation is bypassed to determine if children should be affected

# File lib/ancestry/instance_methods.rb, line 327
def sane_ancestry?
  ancestry_value = read_attribute(self.ancestry_base_class.ancestry_column)
  ancestry_value.nil? || (ancestry_value.to_s =~ Ancestry::ANCESTRY_PATTERN && !ancestor_ids.include?(self.id))
end
unscoped_descendants() click to toggle source
# File lib/ancestry/instance_methods.rb, line 320
def unscoped_descendants
  self.ancestry_base_class.unscoped do
    self.ancestry_base_class.where descendant_conditions
  end
end
unscoped_find(id) click to toggle source
# File lib/ancestry/instance_methods.rb, line 332
def unscoped_find id
  self.ancestry_base_class.unscoped { self.ancestry_base_class.find(id) }
end