module Ancestry::InstanceMethods

def _counter_cache_column

def _counter_cache_column
  self.ancestry_base_class.counter_cache_column.to_s
end

def ancestor_of?(node)

def ancestor_of?(node)
  node.ancestor_ids.include?(self.id)
end

def ancestors depth_options = {}

def ancestors depth_options = {}
  return self.ancestry_base_class.none unless has_parent?
  self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.ancestors_of(self)
end

def ancestry_callbacks_disabled?

def ancestry_callbacks_disabled?
  defined?(@disable_ancestry_callbacks) && @disable_ancestry_callbacks
end

def ancestry_changed?

def ancestry_changed?
  column = self.ancestry_base_class.ancestry_column.to_s
    # These methods return nil if there are no changes.
    # This was fixed in a refactoring in rails 6.0: https://github.com/rails/rails/pull/35933
    !!(will_save_change_to_attribute?(column) || saved_change_to_attribute?(column))
end

def ancestry_exclude_self

Validate that the ancestors don't include itself
def ancestry_exclude_self
  errors.add(:base, I18n.t("ancestry.exclude_self", class_name: self.class.name.humanize)) if ancestor_ids.include? self.id
end

def apply_orphan_strategy

Apply orphan strategy (before destroy - no changes)
def apply_orphan_strategy
  if !ancestry_callbacks_disabled? && !new_record?
    case self.ancestry_base_class.orphan_strategy
    when :rootify # make all children root if orphan strategy is rootify
      unscoped_descendants.each do |descendant|
        descendant.without_ancestry_callbacks do
          descendant.update_attribute :ancestor_ids, descendant.ancestor_ids - path_ids
        end
      end
    when :destroy # destroy all descendants if orphan strategy is destroy
      unscoped_descendants.each do |descendant|
        descendant.without_ancestry_callbacks do
          descendant.destroy
        end
      end
    when :adopt # make child elements of this node, child of its parent
      descendants.each do |descendant|
        descendant.without_ancestry_callbacks do
          descendant.update_attribute :ancestor_ids, (descendant.ancestor_ids.delete_if { |x| x == self.id })
        end
      end
    when :restrict # throw an exception if it has children
      raise Ancestry::AncestryException.new(I18n.t("ancestry.cannot_delete_descendants")) unless is_childless?
    end
  end
end

def cache_depth

def cache_depth
  write_attribute self.ancestry_base_class.depth_cache_column, depth
end

def child_ids

def child_ids
  children.pluck(self.ancestry_base_class.primary_key)
end

def child_of?(node)

def child_of?(node)
  self.parent_id == node.id
end

def children

def children
  self.ancestry_base_class.children_of(self)
end

def decrease_parent_counter_cache

def decrease_parent_counter_cache
  # @_trigger_destroy_callback comes from activerecord, which makes sure only once decrement when concurrent deletion.
  # but @_trigger_destroy_callback began after rails@5.1.0.alpha.
  # https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/persistence.rb#L340
  # https://github.com/rails/rails/pull/14735
  # https://github.com/rails/rails/pull/27248
  return if defined?(@_trigger_destroy_callback) && !@_trigger_destroy_callback
  return if ancestry_callbacks_disabled?
  self.ancestry_base_class.decrement_counter _counter_cache_column, parent_id
end

def depth

def depth
  ancestor_ids.size
end

def descendant_ids depth_options = {}

def descendant_ids depth_options = {}
  descendants(depth_options).pluck(self.ancestry_base_class.primary_key)
end

def descendant_of?(node)

def descendant_of?(node)
  ancestor_ids.include?(node.id)
end

def descendants depth_options = {}

def descendants depth_options = {}
  self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).descendants_of(self)
end

def has_children?

def has_children?
  self.children.exists?
end

def has_parent?

def has_parent?
  ancestor_ids.present?
end

def has_siblings?

def has_siblings?
  self.siblings.count > 1
end

def increase_parent_counter_cache

Counter Cache
def increase_parent_counter_cache
  self.ancestry_base_class.increment_counter _counter_cache_column, parent_id
end

def indirect_ids depth_options = {}

def indirect_ids depth_options = {}
  indirects(depth_options).pluck(self.ancestry_base_class.primary_key)
end

def indirect_of?(node)

def indirect_of?(node)
  ancestor_ids[0..-2].include?(node.id)
end

def indirects depth_options = {}

def indirects depth_options = {}
  self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).indirects_of(self)
end

def is_childless?

def is_childless?
  !has_children?
end

def is_only_child?

def is_only_child?
  !has_siblings?
end

def is_root?

def is_root?
  !has_parent?
end

def parent

def parent
  if has_parent?
    unscoped_where do |scope|
      scope.find_by id: parent_id
    end
  end
end

def parent= parent

assuming that parent hasn't changed
currently parent= does not work in after save callbacks
def parent= parent
  self.ancestor_ids = parent ? parent.path_ids : []
end

def parent_id

def parent_id
  ancestor_ids.last if has_parent?
end

def parent_id= new_parent_id

def parent_id= new_parent_id
  self.parent = new_parent_id.present? ? unscoped_find(new_parent_id) : nil
end

def parent_of?(node)

def parent_of?(node)
  self.id == node.parent_id
end

def path depth_options = {}

def path depth_options = {}
  self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.inpath_of(self)
end

def path_ids

def path_ids
  ancestor_ids + [id]
end

def path_ids_in_database

def path_ids_in_database
  ancestor_ids_in_database + [id]
end

def root

def root
  if has_parent?
    unscoped_where { |scope| scope.find_by(id: root_id) } || self
  else
    self
  end
end

def root_id

def root_id
  has_parent? ? ancestor_ids.first : id
end

def root_of?(node)

def root_of?(node)
  self.id == node.root_id
end

def sane_ancestor_ids?

def sane_ancestor_ids?
  current_context, self.validation_context = validation_context, nil
  errors.clear
  attribute = ancestry_base_class.ancestry_column
  ancestry_value = send(attribute)
  return true unless ancestry_value
  self.class.validators_on(attribute).each do |validator|
    validator.validate_each(self, attribute, ancestry_value)
  end
  ancestry_exclude_self
  errors.none?
ensure
  self.validation_context = current_context
end

def sibling_ids

NOTE: includes self
def sibling_ids
  siblings.pluck(self.ancestry_base_class.primary_key)
end

def sibling_of?(node)

def sibling_of?(node)
  self.ancestor_ids == node.ancestor_ids
end

def siblings

def siblings
  self.ancestry_base_class.siblings_of(self)
end

def subtree depth_options = {}

def subtree depth_options = {}
  self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).subtree_of(self)
end

def subtree_ids depth_options = {}

def subtree_ids depth_options = {}
  subtree(depth_options).pluck(self.ancestry_base_class.primary_key)
end

def touch_ancestors_callback

Touch each of this record's ancestors (after save)
def touch_ancestors_callback
  if !ancestry_callbacks_disabled? && self.ancestry_base_class.touch_ancestors
    # Touch each of the old *and* new ancestors
    unscoped_current_and_previous_ancestors.each do |ancestor|
      ancestor.without_ancestry_callbacks do
        ancestor.touch
      end
    end
  end
end

def unscoped_current_and_previous_ancestors

works with after save context (hence before_last_save)
def unscoped_current_and_previous_ancestors
  unscoped_where do |scope|
    scope.where id: (ancestor_ids + ancestor_ids_before_last_save).uniq
  end
end

def unscoped_descendants

def unscoped_descendants
  unscoped_where do |scope|
    scope.where self.ancestry_base_class.descendant_conditions(self)
  end
end

def unscoped_find id

def unscoped_find id
  unscoped_where do |scope|
    scope.find id
  end
end

def unscoped_where

def unscoped_where
  self.ancestry_base_class.unscoped_where do |scope|
    yield scope
  end
end

def update_descendants_with_new_ancestry

Update descendants with new ancestry (before save)
def update_descendants_with_new_ancestry
  # If enabled and node is existing and ancestry was updated and the new ancestry is sane ...
  if !ancestry_callbacks_disabled? && !new_record? && ancestry_changed? && sane_ancestor_ids?
    # ... for each descendant ...
    unscoped_descendants.each do |descendant|
      # ... replace old ancestry with new ancestry
      descendant.without_ancestry_callbacks do
        new_ancestor_ids = path_ids + (descendant.ancestor_ids - path_ids_in_database)
        descendant.update_attribute(:ancestor_ids, new_ancestor_ids)
      end
    end
  end
end

def update_parent_counter_cache

def update_parent_counter_cache
  changed = saved_change_to_attribute?(self.ancestry_base_class.ancestry_column)
  return unless changed
  if parent_id_was = parent_id_before_last_save
    self.ancestry_base_class.decrement_counter _counter_cache_column, parent_id_was
  end
  parent_id && self.ancestry_base_class.increment_counter(_counter_cache_column, parent_id)
end

def without_ancestry_callbacks

def without_ancestry_callbacks
  @disable_ancestry_callbacks = true
  yield
  @disable_ancestry_callbacks = false
end