0
@@ -5,14 +5,6 @@ module CollectiveIdea
0
base.extend(SingletonMethods)
0
- # better_nested_set ehances the core nested_set tree functionality provided in ruby_on_rails.
0
- # awesome_nested_set goes further, first with a major refactoring
0
- # * almost API-compatible (some broken bits had to be changed)
0
- # * allow it to work with a tree that spans across STI classes
0
- # * removed roots instance method, which was an impossible method.
0
# This acts provides Nested Set functionality. Nested Set is a smart way to implement
0
# an _ordered_ tree, with the added feature that you can select the children and all of their
0
# descendants with a single query. The drawback is that insertion or move need some complex
0
@@ -22,7 +14,8 @@ module CollectiveIdea
0
# commercial categories) or an efficient way of querying big trees (threaded posts).
0
- # Methods names are aligned on Tree's ones as much as possible, to make replacment from one
0
+ # Methods names are aligned with acts_as_tree as much as possible, to make replacment from one
0
# by another easier, except for the creation:
0
@@ -35,33 +28,10 @@ module CollectiveIdea
0
# # now move the item to its right place
0
# child.move_to_child_of my_item
0
- # and pass them an id or an object.
0
- # Other methods added by this mixin are:
0
- # * +root+ - root item of the tree (the one that has a nil parent; should have left_column = 1 too)
0
- # * +roots+ - root items, in case of multiple roots (the ones that have a nil parent)
0
- # * +level+ - number indicating the level, a root being level 0
0
- # * +ancestors+ - array of all parents, with root as first item
0
- # * +self_and_ancestors+ - array of all parents and self
0
- # * +siblings+ - array of all siblings, that are the items sharing the same parent and level
0
- # * +self_and_siblings+ - array of itself and all siblings
0
- # * +children_count+ - count of all immediate children
0
- # * +children+ - array of all immediate childrens
0
- # * +all_children+ - array of all children and nested children
0
- # * +full_set+ - array of itself and all children and nested children
0
- # These should not be useful, except if you want to write direct SQL:
0
- # * +left_column_name+ - name of the left column passed on the declaration line
0
- # * +right_column_name+ - name of the right column passed on the declaration line
0
- # * +parent_column_name+ - name of the parent column passed on the declaration line
0
- # Don't name your left and right columns 'left' and 'right': these names are reserved on most of dbs.
0
- # Usage is to name them 'lft' and 'rgt' for instance.
0
+ # You can pass an id or an object to:
0
+ # * <tt>#move_to_child_of</tt>
0
+ # * <tt>#move_to_right_of</tt>
0
+ # * <tt>#move_to_left_of</tt>
0
module SingletonMethods
0
# Configuration options are:
0
@@ -73,6 +43,10 @@ module CollectiveIdea
0
# (if that hasn't been already) and use that as the foreign key restriction. It's also possible
0
# to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
0
# Example: <tt>acts_as_nested_set :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
0
+ # See CollectiveIdea::Acts::NestedSet::ClassMethods for a list of class methods and
0
+ # CollectiveIdea::Acts::NestedSet::InstanceMethods for a list of instance methods added
0
+ # to acts_as_nested_set models
0
def acts_as_nested_set(options = {})
0
:parent_column => 'parent_id',
0
@@ -89,8 +63,8 @@ module CollectiveIdea
0
include InstanceMethods
0
@@ -109,30 +83,24 @@ module CollectiveIdea
0
+ named_scope :roots, :conditions => {parent_column_name => nil}, :order => left_column_name
0
- def roots(multiplicity = :all, *args)
0
- with_scope(:find => {:conditions => {parent_column_name => nil},
0
- :order => left_column_name }) do
0
- find(multiplicity, *args)
0
# Returns the first root
0
left_and_rights_valid? &&
0
- no_duplicates_for_column?(quoted_left_column_name) &&
0
- no_duplicates_for_column?(quoted_right_column_name) &&
0
+ no_duplicates_for_column?(quoted_left_column_name) &&
0
+ no_duplicates_for_column?(quoted_right_column_name) &&
0
def left_and_rights_valid?
0
@@ -155,7 +123,7 @@ module CollectiveIdea
0
# pass in quoted_left_column_name or quoted_right_column_name
0
def no_duplicates_for_column?(column)
0
- scope_string =
"#{quoted_scope_column_name+', ' if acts_as_nested_set_options[:scope]}"
0
+ scope_string =
acts_as_nested_set_options[:scope] ? '' : "#{quoted_scope_column_name}, "
0
:select => "#{scope_string}#{column}, COUNT(#{column})",
0
@@ -214,8 +182,8 @@ module CollectiveIdea
0
- # Mixed into both classes and instances
0
+ # Mixed into both classes and instances to provide easy access to the column names
0
acts_as_nested_set_options[:left_column]
0
@@ -249,16 +217,25 @@ module CollectiveIdea
0
+ # Any instance method that returns a collection makes use of Rails 2.1's named_scope (which is bundled for Rails 2.0), so it can be treated as a finder.
0
+ # category.self_and_descendants.count
0
+ # category.ancestors.find(:all, :conditions => "name like '%foo%'")
0
+ # alias ActiveRecord::Base::Scope so we don't always have to refer to it with the long name
0
+ Scope = ActiveRecord::Base::Scope
0
+ # Value of the parent column
0
self[parent_column_name]
0
+ # Value of the left column
0
+ # Value of the right column
0
self[right_column_name]
0
@@ -270,7 +247,7 @@ module CollectiveIdea
0
# Returns true is this is a child node
0
- !parent_id.nil?
&& left > 10
@@ -322,41 +299,32 @@ module CollectiveIdea
0
- self_and_ancestors
(:first)
0
+ self_and_ancestors
.find(:first)
0
+ # Returns the
immediate parent
0
-
self.class.base_class.find_by_id(parent_id) if parent_id
0
+
nested_set_scope.find_by_id(parent_id) if parent_id
0
# Returns the array of all parents and self
0
- def self_and_ancestors(multiplicity = :all, *args)
0
- with_nested_set_scope do
0
- with_find_scope(:conditions => "#{left_column_name} <= #{left} AND #{right_column_name} >= #{right}") { self.class.base_class.find(multiplicity, *args) }
0
+ def self_and_ancestors
0
+ Scope.new(nested_set_scope, :conditions => "#{left_column_name} <= #{left} AND #{right_column_name} >= #{right}")
0
# Returns an array of all parents
0
- without_self { self_and_ancestors(*args) }
0
+ without_self self_and_ancestors
0
# Returns the array of all children of the parent, including self
0
- def self_and_siblings(multiplicity = :all, *args)
0
- with_nested_set_scope do
0
- scope = if parent_id.nil?
0
- {self.class.base_class.primary_key => self}
0
- {parent_column_name => parent_id}
0
- with_find_scope(:conditions => scope) { self.class.base_class.find(multiplicity, *args) }
0
# Returns the array of all children of the parent, except self
0
- without_self { self_and_siblings(*args) }
0
+ without_self self_and_siblings
0
# Returns the level of this object in the tree
0
@@ -365,102 +333,74 @@ module CollectiveIdea
0
- with_nested_set_scope do
0
- self.class.base_class.count(:conditions => "(#{left_column_name} < #{left} AND #{right_column_name} > #{right})")
0
+ nested_set_scope.count(:conditions => "(#{left_column_name} < #{left} AND #{right_column_name} > #{right})")
0
- # Returns the number of nested children of this object.
0
- # FIXME: rename to descendants.count
0
- (right - left - 1) / 2
0
# Returns a set of itself and all of its nested children
0
- def self_and_descendants(multiplicity = :all, *args)
0
- with_nested_set_scope do
0
- with_find_scope(:conditions => "#{left_column_name} >= #{left}
0
- AND #{right_column_name} <= #{right}"
0
- ) { self.class.base_class.find(multiplicity, *args) }
0
+ def self_and_descendants
0
+ Scope.new(nested_set_scope,
0
+ :conditions => "#{left_column_name} >= #{left} AND #{right_column_name} <= #{right}")
0
# Returns a set of all of its children and nested children
0
- def descendants(*args)
0
- without_self { self_and_descendants(*args) }
0
+ without_self self_and_descendants
0
# Returns a set of only this entry's immediate children
0
- def children(multiplicity = :all, *args)
0
- with_nested_set_scope do
0
- with_find_scope(:conditions => {parent_column_name => self}) do
0
- self.class.base_class.find(multiplicity, *args)
0
+ Scope.new(nested_set_scope, :conditions => {parent_column_name => self})
0
def is_descendant_of?(other)
0
- other.left < self.left && self.left < other.right &&
is_same_scope?(other)
0
+ other.left < self.left && self.left < other.right &&
same_scope?(other)
0
def is_or_is_descendant_of?(other)
0
- other.left <= self.left && self.left < other.right &&
is_same_scope?(other)
0
+ other.left <= self.left && self.left < other.right &&
same_scope?(other)
0
def is_ancestor_of?(other)
0
- self.left < other.left && other.left < self.right &&
is_same_scope?(other)
0
+ self.left < other.left && other.left < self.right &&
same_scope?(other)
0
def is_or_is_ancestor_of?(other)
0
- self.left <= other.left && other.left < self.right &&
is_same_scope?(other)
0
+ self.left <= other.left && other.left < self.right &&
same_scope?(other)
0
- def is_same_scope?(other)
0
- !acts_as_nested_set_options[:scope] || self.send(scope_column_name) == other.send(scope_column_name)
0
+ # Check if other model is in the same scope
0
+ def same_scope?(other)
0
+ !acts_as_nested_set_options[:scope] ||
0
+ self.send(scope_column_name) == other.send(scope_column_name)
0
# Find the first sibling to the right
0
- with_nested_set_scope do
0
- self.class.base_class.find(:first,
0
- :conditions => ["#{quoted_left_column_name} < ? " +
0
- "AND #{quoted_parent_column_name} = ?", left, parent_id],
0
- :order => "#{left_column_name} DESC")
0
+ nested_set_scope.find(:first,
0
+ :conditions => ["#{quoted_left_column_name} < ? AND #{quoted_parent_column_name} = ?",
0
+ :order => "#{left_column_name} DESC"
0
# Find the first sibling to the right
0
- with_nested_set_scope do
0
- self.class.base_class.find(:first,
0
- :conditions => ["#{quoted_left_column_name} > ? " +
0
- "AND #{quoted_parent_column_name} = ?", left, parent_id],
0
- :order => left_column_name
0
+ nested_set_scope.find(:first,
0
+ :conditions => ["#{quoted_left_column_name} > ? AND #{quoted_parent_column_name} = ?",
0
+ :order => left_column_name
0
# Shorthand method for finding the left sibling and moving to the left of it.
0
- move_to_left_of
(left_sibling)0
+ move_to_left_of
left_sibling0
# Shorthand method for finding the right sibling and moving to the right of it.
0
- move_to_right_of
(right_sibling)0
+ move_to_right_of
right_sibling0
# Move the node to the left of another node (you can pass id only)
0
@@ -482,7 +422,7 @@ module CollectiveIdea
0
# can't be in different scopes
0
-
(!acts_as_nested_set_options[:scope] || self.send(scope_column_name) == target.send(scope_column_name)) && 0
+
same_scope?(target) &&0
# detect impossible move
0
# !(left..right).include?(target.left..target.right) # this needs tested more
0
!((left <= target.left && right >= target.left) or (left <= target.right && right >= target.right))
0
@@ -496,27 +436,24 @@ module CollectiveIdea
0
- with_find_scope(:conditions => ["#{self.class.primary_key} != ?", self]) do
0
+ def without_self(scope)
0
+ Scope.new(scope, :conditions => ["#{self.class.primary_key} != ?", self])
0
- def with_find_scope(scope)
0
- self.class.base_class.send(:with_scope, :find => scope) { yield }
0
- def with_nested_set_scope
0
- scope = {:order => left_column_name}
0
+ # All nested set queries should use this nested_set_scope, which performs finds on
0
+ # the base ActiveRecord class, using the :scope declared in the acts_as_nested_set
0
+ options = {:order => left_column_name}
0
if scope_column = acts_as_nested_set_options[:scope]
0
-
scope[:conditions] = {scope_column => self[scope_column]}
0
+
options[:conditions] = {scope_column => self[scope_column]}
0
-
self.class.base_class.send(:with_scope, :find => scope) { yield }0
+
Scope.new(self.class.base_class, options)0
# on creation, set automatically lft and rgt to the end of the tree
0
def set_default_left_and_right
0
- maxright =
with_nested_set_scope { self.class.base_class.maximum(right_column_name) } || 0
0
+ maxright =
nested_set_scope.maximum(right_column_name) || 0
0
# adds the new node to the right of all existing nodes
0
self[left_column_name] = maxright + 1
0
self[right_column_name] = maxright + 2
0
@@ -528,14 +465,12 @@ module CollectiveIdea
0
return if right.nil? || left.nil?
0
diff = right - left + 1
0
- with_nested_set_scope do
0
- self.class.base_class.transaction do
0
- self.class.base_class.delete_all("#{left_column_name} > #{left} AND #{right_column_name} < #{right}")
0
- self.class.base_class.update_all("#{left_column_name} = (#{left_column_name} - #{diff})",
0
- "#{left_column_name} >= #{right}")
0
- self.class.base_class.update_all("#{right_column_name} = (#{right_column_name} - #{diff} )",
0
- "#{right_column_name} >= #{right}" )
0
+ self.class.base_class.transaction do
0
+ nested_set_scope.delete_all("#{left_column_name} > #{left} AND #{right_column_name} < #{right}")
0
+ nested_set_scope.update_all("#{left_column_name} = (#{left_column_name} - #{diff})",
0
+ "#{left_column_name} >= #{right}")
0
+ nested_set_scope.update_all("#{right_column_name} = (#{right_column_name} - #{diff} )",
0
+ "#{right_column_name} >= #{right}" )