module SymetrieCom::Acts::NestedSet::InstanceMethods

This module provides instance methods for an enhanced acts_as_nested_set mixin. Please see the README for background information, examples, and tips on usage.

Public Instance Methods

<=>(x) click to toggle source

By default, records are compared and sorted using the left column.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 450
def <=>(x)
  self[left_col_name] <=> x[left_col_name]
end
add_child(child) click to toggle source

Deprecated. Adds a child to this object in the tree. If this object hasn’t been initialized, it gets set up as a root node.

This method exists only for compatibility and will be removed in future versions.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 754
def add_child(child)
  transaction do
    self.reload; child.reload # for compatibility with old version
    # the old version allows records with nil values for lft and rgt
    unless self[left_col_name] && self[right_col_name]
      if child[left_col_name] || child[right_col_name]
        raise ActiveRecord::ActiveRecordError, "If parent lft or rgt are nil, you can't add a child with non-nil lft or rgt"
      end
      base_set_class.update_all("#{left_col_name} = CASE \
                                  WHEN id = #{self.id} \
                                    THEN 1 \
                                  WHEN id = #{child.id} \
                                    THEN 3 \
                                  ELSE #{left_col_name} END, \
                             #{right_col_name} = CASE \
                                  WHEN id = #{self.id} \
                                    THEN 2 \
                                  WHEN id = #{child.id} \
                                    THEN 4 \
                                 ELSE #{right_col_name} END",
                              scope_condition)
      self.reload; child.reload
    end
    unless child[left_col_name] && child[right_col_name]
      maxright = base_set_class.maximum(right_col_name, :conditions => scope_condition) || 0
      base_set_class.update_all("#{left_col_name} = CASE \
                                  WHEN id = #{child.id} \
                                    THEN #{maxright + 1} \
                                  ELSE #{left_col_name} END, \
                              #{right_col_name} = CASE \
                                  WHEN id = #{child.id} \
                                    THEN #{maxright + 2} \
                                  ELSE #{right_col_name} END",
                              scope_condition)
      child.reload
    end
    
    child.move_to_child_of(self)
    # self.reload ## even though move_to calls target.reload, at least one object in the tests was not reloading (near the end of test_common_usage)
  end
# self.reload
# child.reload
#
# if child.root?
#   raise ActiveRecord::ActiveRecordError, "Adding sub-tree isn\'t currently supported"
# else
#   if ( (self[left_col_name] == nil) || (self[right_col_name] == nil) )
#     # Looks like we're now the root node!  Woo
#     self[left_col_name] = 1
#     self[right_col_name] = 4
#
#     # What do to do about validation?
#     return nil unless self.save
#
#     child[parent_col_name] = self.id
#     child[left_col_name] = 2
#     child[right_col_name]= 3
#     return child.save
#   else
#     # OK, we need to add and shift everything else to the right
#     child[parent_col_name] = self.id
#     right_bound = self[right_col_name]
#     child[left_col_name] = right_bound
#     child[right_col_name] = right_bound + 1
#     self[right_col_name] += 2
#     self.class.transaction {
#       self.class.update_all( "#{left_col_name} = (#{left_col_name} + 2)",  "#{scope_condition} AND #{left_col_name} >= #{right_bound}" )
#       self.class.update_all( "#{right_col_name} = (#{right_col_name} + 2)",  "#{scope_condition} AND #{right_col_name} >= #{right_bound}" )
#       self.save
#       child.save
#     }
#   end
# end
end
all_children(scope = {}) click to toggle source

Returns all children and nested children. Pass :exclude => item, or id, or [items or id] to exclude one or more items and all of their descendants.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 634
def all_children(scope = {})
  full_set(scope) - [self]
end
all_children_count(scope = nil) click to toggle source

Returns the number of nested children of this object.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 567
def all_children_count(scope = nil)
  return all_children(scope).length if scope.is_a?(Hash)
  return (self[right_col_name] - self[left_col_name] - 1)/2
end
all_children_through(other, scope = {}) click to toggle source

All children until the other is reached - excluding self

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 679
def all_children_through(other, scope = {})
  full_set_through(other, scope) - [self]
end
ancestors(scope = {}) click to toggle source

Returns an array of all parents, starting with the root.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 489
def ancestors(scope = {})
  self_and_ancestors(scope) - [self]
end
ancestors_and_self_through(other, scope = {}) click to toggle source

All nodes between two nodes, those nodes included in effect all ancestors until the other is reached

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 667
def ancestors_and_self_through(other, scope = {})
  first, last = [self, other].sort
  self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND (#{last[left_col_name]} BETWEEN #{prefixed_left_col_name} AND #{prefixed_right_col_name}) AND #{prefixed_left_col_name} >= #{first[left_col_name]}", 
    :order => "#{prefixed_left_col_name}" }, scope)
end
ancestors_through(other, scope = {}) click to toggle source

Ancestors until the other is reached - excluding self

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 674
def ancestors_through(other, scope = {})
  ancestors_and_self_through(other, scope) - [self]
end
check_full_tree() click to toggle source

Checks the left/right indexes of the entire tree that this node belongs to, returning the number of records checked. Throws ActiveRecord::ActiveRecordError if it finds a problem. This method is needed because check_subtree alone cannot find gaps between virtual roots, orphaned nodes or endless loops.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 717
def check_full_tree
  total_nodes = 0
  transaction do
    # virtual roots make this method more complex than it otherwise would be
    n = 1
    roots.each do |r| 
      raise ActiveRecord::ActiveRecordError, "Gaps between roots in the tree containing record ##{r.id}" if r[left_col_name] != n
      r.check_subtree
      n = r[right_col_name] + 1
    end
    total_nodes = roots.inject(0) {|sum, r| sum + r.all_children_count + 1 }
    unless base_set_class.count(:conditions => "#{scope_condition}") == total_nodes
      raise ActiveRecord::ActiveRecordError, "Orphaned nodes or endless loops in the tree containing record ##{self.id}"
    end
  end
  return total_nodes
end
check_subtree() click to toggle source

Checks the left/right indexes of one node and all descendants. Throws ActiveRecord::ActiveRecordError if it finds a problem.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 707
def check_subtree
  transaction do
    self.reload
    check # this method is implemented via #check, so that we don't generate lots of unnecessary nested transactions
  end
end
child?() click to toggle source

Deprecated. Returns true if this is a child node

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 461
def child?                          
  parent_id = self[parent_col_name]
  !(parent_id == 0 || parent_id.nil?) && (self[left_col_name] > 1) && (self[right_col_name] > self[left_col_name])
end
child_by_id(id, scope = {}) click to toggle source

Returns the child for the requested id within the scope of its children, otherwise nil

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 587
def child_by_id(id, scope = {})
  children_by_id(id, scope).first
end
child_of?(parent, scope = {}) click to toggle source

Tests wether self is within scope of parent

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 615
def child_of?(parent, scope = {})
  if !scope.empty? && parent.respond_to?(:child_by_id)
    parent.child_by_id(self.id, scope).is_a?(self.class)
  else
    parent.respond_to?(left_col_name) && self[left_col_name] > parent[left_col_name] && self[right_col_name] < parent[right_col_name]
  end
end
children(scope = {}) click to toggle source

Returns this record’s immediate children.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 643
def children(scope = {})
  self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND #{prefixed_parent_col_name} = #{self.id}", :order => "#{prefixed_left_col_name}" }, scope)
end
Also aliased as: direct_children
children?(scope = {}) click to toggle source
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 647
def children?(scope = {})
  children_count(scope) > 0
end
children_by_id(*args) click to toggle source

Returns a child collection for the requested ids within the scope of its children, otherwise empty array

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 592
def children_by_id(*args)
  scope = args.last.is_a?(Hash) ? args.pop : {}
  ids = args.flatten.compact.uniq
  self.class.find_in_nested_set(:all, { 
    :conditions => ["#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{self.class.table_name}.#{self.class.primary_key} IN (?)", ids] 
  }, scope)
end
children_count(scope= {}) click to toggle source
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 638
def children_count(scope= {})
  self.class.count_in_nested_set({ :conditions => "#{scope_condition} AND #{prefixed_parent_col_name} = #{self.id}" }, scope)
end
destroy_descendants() click to toggle source

On destruction, delete all children and shift the lft/rgt values back to the left so the counts still work.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 429
def destroy_descendants # already protected by a transaction within #destroy
  return if self[right_col_name].nil? || self[left_col_name].nil? || self.skip_before_destroy
  reloaded = self.reload rescue nil # in case a concurrent move has altered the indexes - rescue if non-existent
  return unless reloaded
  dif = self[right_col_name] - self[left_col_name] + 1
  if acts_as_nested_set_options[:dependent] == :delete_all
    base_set_class.delete_all( "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})" )          
  else 
    set = base_set_class.find(:all, :conditions => "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})", :order => "#{prefixed_right_col_name} DESC")     
    set.each { |child| child.skip_before_destroy = true; remove_descendant(child) } 
  end
  base_set_class.update_all("#{left_col_name} = CASE \
                              WHEN #{left_col_name} > #{self[right_col_name]} THEN (#{left_col_name} - #{dif}) \
                              ELSE #{left_col_name} END, \
                         #{right_col_name} = CASE \
                              WHEN #{right_col_name} > #{self[right_col_name]} THEN (#{right_col_name} - #{dif} ) \
                              ELSE #{right_col_name} END",
                         scope_condition)
end
direct_child_by_id(id, scope = {}) click to toggle source

Returns the child for the requested id within the scope of its immediate children, otherwise nil

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 601
def direct_child_by_id(id, scope = {})
  direct_children_by_id(id, scope).first
end
direct_child_of?(parent, scope = {}) click to toggle source

Tests wether self is within immediate scope of parent

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 624
def direct_child_of?(parent, scope = {})
  if !scope.empty? && parent.respond_to?(:direct_child_by_id)
    parent.direct_child_by_id(self.id, scope).is_a?(self.class)
  else
    parent.respond_to?(parent_col_name) && self[parent_col_name] == parent.id
  end
end
direct_children(scope = {})

Deprecated

Alias for: children
direct_children_by_id(*args) click to toggle source

Returns a child collection for the requested ids within the scope of its immediate children, otherwise empty array

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 606
def direct_children_by_id(*args)
  scope = args.last.is_a?(Hash) ? args.pop : {}
  ids = args.flatten.compact.uniq
  self.class.find_in_nested_set(:all, { 
    :conditions => ["#{scope_condition} AND #{prefixed_parent_col_name} = #{self.id} AND #{self.class.table_name}.#{self.class.primary_key} IN (?)", ids]
  }, scope)          
end
first?(scope = {})
Alias for: first_sibling?
first_sibling(scope = {}) click to toggle source

Returns first siblings amongst it’s siblings.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 516
def first_sibling(scope = {})
  self_and_siblings(scope.merge(:limit => 1, :order => "#{prefixed_left_col_name} ASC")).first
end
first_sibling?(scope = {}) click to toggle source
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 520
def first_sibling?(scope = {})
  self == first_sibling(scope)
end
Also aliased as: first?
full_set(scope = {}) click to toggle source

Returns itself and all nested children. Pass :exclude => item, or id, or [items or id] to exclude one or more items and all of their descendants.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 574
def full_set(scope = {})
  if exclude = scope.delete(:exclude)
    exclude_str = " AND NOT (#{base_set_class.sql_for(exclude)}) "
  elsif new_record? || self[right_col_name] - self[left_col_name] == 1
    return [self]
  end
  self.class.find_in_nested_set(:all, { 
    :order => "#{prefixed_left_col_name}",
    :conditions => "#{scope_condition} #{exclude_str} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})"
  }, scope)
end
full_set_through(other, scope = {}) click to toggle source

All children until the other is reached - including self

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 684
def full_set_through(other, scope = {})
  first, last = [self, other].sort
  self.class.find_in_nested_set(:all,  
    { :conditions => "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{first[left_col_name]} AND #{first[right_col_name]}) AND #{prefixed_left_col_name} <= #{last[left_col_name]}", :order => "#{prefixed_left_col_name}" }, scope)
end
higher_item(num = 1, scope = {})
Alias for: previous_sibling
insert_at(target, index = :last, scope = {}) click to toggle source

Insert a node at a specific position among the children of target.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 830
def insert_at(target, index = :last, scope = {})
  level_nodes = target.children(scope)
  current_index = level_nodes.index(self)
  last_index = level_nodes.length - 1 
  as_first = (index == :first)
  as_last  = (index == :last || (index.is_a?(Fixnum) && index > last_index))         
  index = 0 if as_first
  index = last_index if as_last
  if last_index < 0
    move_to_child_of(target)
  elsif index >= 0 && index <= last_index && level_nodes[index]            
    if as_last && index != current_index
      move_to_right_of(level_nodes[index])
    elsif (as_first || index == 0) && index != current_index
      move_to_left_of(level_nodes[index])
    elsif !current_index.nil? && index > current_index
      move_to_right_of(level_nodes[index])
    elsif !current_index.nil? && index < current_index
      move_to_left_of(level_nodes[index])
    elsif current_index.nil?
      move_to_left_of(level_nodes[index])
    end        
  end
end
last?(scope = {})
Alias for: last_sibling?
last_sibling(scope = {}) click to toggle source

Returns last siblings amongst it’s siblings.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 526
def last_sibling(scope = {})
  self_and_siblings(scope.merge(:limit => 1, :order => "#{prefixed_left_col_name} DESC")).first
end
last_sibling?(scope = {}) click to toggle source
# File lib/symetrie_com/acts_as_better_nested_set.rb, line 530
def last_sibling?(scope = {})
  self == last_sibling(scope)
end
Also aliased as: last?
leaves(scope = {}) click to toggle source

Returns this record’s terminal children (nodes without children).

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 655
def leaves(scope = {})
  self.class.find_in_nested_set(:all, 
    { :conditions => "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{prefixed_left_col_name} + 1 = #{prefixed_right_col_name}", :order => "#{prefixed_left_col_name}" }, scope)
end
leaves_count(scope = {}) click to toggle source

Returns the count of this record’s terminal children (nodes without children).

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 661
def leaves_count(scope = {})
  self.class.count_in_nested_set({ :conditions => "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]}) AND #{prefixed_left_col_name} + 1 = #{prefixed_right_col_name}" }, scope)
end
level(scope = {}) click to toggle source

Returns the level of this object in the tree, root level being 0.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 561
def level(scope = {})
  return 0 if self[parent_col_name].nil?
  self.class.count_in_nested_set({ :conditions => "#{scope_condition} AND (#{self[left_col_name]} BETWEEN #{prefixed_left_col_name} AND #{prefixed_right_col_name})" }, scope) - 1
end
lower_item(num = 1, scope = {})
Alias for: next_sibling
move_higher() click to toggle source

Moves a node one down amongst its siblings. Does nothing if it’s already the last sibling.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 887
def move_higher         
  prev_sib = previous_sibling
  move_to_left_of(prev_sib) if prev_sib
end
move_lower() click to toggle source

Moves a node one up amongst its siblings. Does nothing if it’s already the first sibling.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 880
def move_lower
  next_sib = next_sibling
  move_to_right_of(next_sib) if next_sib
end
move_to_bottom() click to toggle source

Moves a node one to be the last amongst its siblings. Does nothing if it’s already the last sibling.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 901
def move_to_bottom
  last_sib = last_sibling
  move_to_right_of(last_sib) if last_sib && self != last_sib
end
move_to_child_of(target) click to toggle source

Make this node a child of target (you can pass an object or just an id). Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 869
def move_to_child_of(target)
  self.move_to target, :child
end
move_to_left_of(target) click to toggle source

Move this node to the left of target (you can pass an object or just an id). Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 857
def move_to_left_of(target)
  self.move_to target, :left
end
move_to_position(index, scope = {}) click to toggle source

Moves a node to a certain position amongst its siblings.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 874
def move_to_position(index, scope = {})
  insert_at(self.parent, index, scope)
end
move_to_right_of(target) click to toggle source

Move this node to the right of target (you can pass an object or just an id). Unsaved changes in either object will be lost. Raises ActiveRecord::ActiveRecordError if it encounters a problem.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 863
def move_to_right_of(target)
  self.move_to target, :right
end
move_to_top() click to toggle source

Moves a node one to be the first amongst its siblings. Does nothing if it’s already the first sibling.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 894
def move_to_top
  first_sib = first_sibling
  move_to_left_of(first_sib) if first_sib && self != first_sib
end
next_sibling(num = 1, scope = {}) click to toggle source

Returns next sibling of node or nil if there is none.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 544
def next_sibling(num = 1, scope = {})
  scope[:limit] = num
  siblings = next_siblings(scope)
  num == 1 ? siblings.first : siblings
end
Also aliased as: lower_item
next_siblings(scope = {}) click to toggle source

Returns all siblings to the right of self, in ascending order, so the first sibling is the one closest to the right of self

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 510
def next_siblings(scope = {})
  self.class.find_in_nested_set(:all, 
    { :conditions => ["#{scope_condition} AND #{sibling_condition} AND #{self.class.table_name}.id != ? AND #{prefixed_left_col_name} > ?", self.id, self[right_col_name]], :order => "#{prefixed_left_col_name} ASC"}, scope)
end
parent() click to toggle source

Returns this record’s parent.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 484
def parent
  self.class.find_in_nested_set(self[parent_col_name]) if self[parent_col_name]
end
previous_sibling(num = 1, scope = {}) click to toggle source

Returns previous sibling of node or nil if there is none.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 536
def previous_sibling(num = 1, scope = {})
  scope[:limit] = num
  siblings = previous_siblings(scope)
  num == 1 ? siblings.first : siblings
end
Also aliased as: higher_item
previous_siblings(scope = {}) click to toggle source

Returns all siblings to the left of self, in descending order, so the first sibling is the one closest to the left of self

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 504
def previous_siblings(scope = {})
  self.class.find_in_nested_set(:all, 
    { :conditions => ["#{scope_condition} AND #{sibling_condition} AND #{self.class.table_name}.id != ? AND #{prefixed_right_col_name} < ?", self.id, self[left_col_name]], :order => "#{prefixed_left_col_name} DESC" }, scope)
end
renumber_full_tree() click to toggle source

Re-calculate the left/right values of all nodes in this record’s tree. Can be used to convert an ordinary tree into a nested set.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 736
def renumber_full_tree
  indexes = []
  n = 1
  transaction do
    for r in roots # because we may have virtual roots
      n = 1 + r.calc_numbers(n, indexes)
    end
    for i in indexes
      base_set_class.update_all("#{left_col_name} = #{i[:lft]}, #{right_col_name} = #{i[:rgt]}", "#{self.class.primary_key} = #{i[:id]}")
    end
  end
  ## reload?
end
reorder_children(*ids) click to toggle source

Reorder children according to an array of ids

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 913
def reorder_children(*ids)
  transaction do
    ordered_ids = ids.flatten.uniq
    current_children = children({ :conditions => { :id => ordered_ids } })
    current_children_ids = current_children.map(&:id)
    ordered_ids = ordered_ids & current_children_ids
    return [] unless ordered_ids.length > 1 && ordered_ids != current_children_ids
    perform_reorder_of_children(ordered_ids, current_children)
  end         
end
root(scope = {}) click to toggle source

Returns this record’s root ancestor.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 472
def root(scope = {})
  # the BETWEEN clause is needed to ensure we get the right virtual root, if using those
  self.class.find_in_nested_set(:first, { :conditions => "#{scope_condition} \
    AND (#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0) AND (#{self[left_col_name]} BETWEEN #{prefixed_left_col_name} AND #{prefixed_right_col_name})" }, scope)
end
root?() click to toggle source

Deprecated. Returns true if this is a root node.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 455
def root?
  parent_id = self[parent_col_name]
  (parent_id == 0 || parent_id.nil?) && self[right_col_name] && self[left_col_name] && (self[right_col_name] > self[left_col_name])
end
roots(scope = {}) click to toggle source

Returns the root or virtual roots of this record’s tree (a tree cannot have more than one real root). See the explanation of virtual roots in the README.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 479
def roots(scope = {})
  self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND (#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0)", :order => "#{prefixed_left_col_name}" }, scope)
end
self_and_ancestors(scope = {}) click to toggle source

Returns an array of all parents plus self, starting with the root.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 494
def self_and_ancestors(scope = {})
  self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND (#{self[left_col_name]} BETWEEN #{prefixed_left_col_name} AND #{prefixed_right_col_name})", :order => "#{prefixed_left_col_name}" }, scope)
end
self_and_siblings(scope = {}) click to toggle source

Returns all the children of this node’s parent, including self.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 552
def self_and_siblings(scope = {})
  if self[parent_col_name].nil? || self[parent_col_name].zero?
    [self]
  else
    self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND #{sibling_condition}", :order => "#{prefixed_left_col_name}" }, scope)
  end
end
self_and_siblings_through(other, scope = {}) click to toggle source

All siblings until the other is reached - including self

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 691
def self_and_siblings_through(other, scope = {})
  if self[parent_col_name].nil? || self[parent_col_name].zero?
    [self]
  else
    first, last = [self, other].sort
    self.class.find_in_nested_set(:all, { :conditions => "#{scope_condition} AND #{sibling_condition} AND (#{prefixed_left_col_name} BETWEEN #{first[left_col_name]} AND #{last[right_col_name]})", :order => "#{prefixed_left_col_name}" }, scope)
  end
end
set_left_right() click to toggle source

On creation, automatically add the new node to the right of all existing nodes in this tree.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 422
def set_left_right # already protected by a transaction within #create
  maxright = base_set_class.maximum(right_col_name, :conditions => scope_condition) || 0
  self[left_col_name] = maxright+1
  self[right_col_name] = maxright+2
end
sibling_condition() click to toggle source

This takes care of valid queries when called on a root node

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 417
def sibling_condition
  self[parent_col_name] ? "#{prefixed_parent_col_name} = #{self[parent_col_name]}" : "(#{prefixed_parent_col_name} IS NULL OR #{prefixed_parent_col_name} = 0)"
end
siblings(scope = {}) click to toggle source

Returns all the children of this node’s parent, except self.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 499
def siblings(scope = {})
  self_and_siblings(scope) - [self]
end
siblings_through(other, scope = {}) click to toggle source

All siblings until the other is reached - excluding self

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 701
def siblings_through(other, scope = {})
  self_and_siblings_through(other, scope) - [self]
end
swap(target, transact = true) click to toggle source

Swaps the position of two sibling nodes preserving a sibling’s descendants. The current implementation only works amongst siblings.

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 908
def swap(target, transact = true)
  move_to(target, :swap, transact)     
end
unknown?() click to toggle source

Deprecated. Returns true if we have no idea what this is

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 467
def unknown?
  !root? && !child?
end

Protected Instance Methods

calculate_reorder_steps(ordered_ids, current) click to toggle source

Calculate the least amount of swap steps to achieve the requested order

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 1079
def calculate_reorder_steps(ordered_ids, current)
  steps = []
  current.each_with_index do |source, idx|
    new_idx = ordered_ids.index(source.id)
    steps << [source, new_idx] if idx != new_idx
  end
  steps
end
perform_reorder_of_children(ordered_ids, current) click to toggle source

Actually perform the ordering using calculated steps

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 1064
def perform_reorder_of_children(ordered_ids, current)
  steps = calculate_reorder_steps(ordered_ids, current)
  steps.inject([]) do |result, (source, idx)|
    target = current[idx]
    if source.id != target.id            
      source.swap(target, false)             
      from = current.index(source)
      current[from], current[idx] = current[idx], current[from]
      result << source
    end
    result
  end
end

Private Instance Methods

remove_descendant(descendant) click to toggle source

as a seperate method to facilitate custom implementations based on :dependent option

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 1122
def remove_descendant(descendant)
  descendant.destroy
end
with_optional_transaction(bool) { || ... } click to toggle source

optionally use a transaction

# File lib/symetrie_com/acts_as_better_nested_set.rb, line 1117
def with_optional_transaction(bool, &block)
  bool ? transaction { yield } : yield
end