Returns an array of all parents
# File lib/awesome_nested_set/awesome_nested_set.rb, line 342 def ancestors without_self self_and_ancestors end
Returns true is this is a child node
# File lib/awesome_nested_set/awesome_nested_set.rb, line 317 def child? !root? end
Returns a set of all of its children and nested children
# File lib/awesome_nested_set/awesome_nested_set.rb, line 376 def descendants without_self self_and_descendants end
# File lib/awesome_nested_set/awesome_nested_set.rb, line 388 def is_ancestor_of?(other) self.left < other.left && other.left < self.right && same_scope?(other) end
# File lib/awesome_nested_set/awesome_nested_set.rb, line 380 def is_descendant_of?(other) other.left < self.left && self.left < other.right && same_scope?(other) end
# File lib/awesome_nested_set/awesome_nested_set.rb, line 392 def is_or_is_ancestor_of?(other) self.left <= other.left && other.left < self.right && same_scope?(other) end
# File lib/awesome_nested_set/awesome_nested_set.rb, line 384 def is_or_is_descendant_of?(other) other.left <= self.left && self.left < other.right && same_scope?(other) end
Returns true if this is the end of a branch.
# File lib/awesome_nested_set/awesome_nested_set.rb, line 312 def leaf? persisted? && right.to_i - left.to_i == 1 end
Returns a set of all of its nested children which do not have children
# File lib/awesome_nested_set/awesome_nested_set.rb, line 357 def leaves descendants.where("#{quoted_right_column_full_name} - #{quoted_left_column_full_name} = 1") end
Value of the left column
# File lib/awesome_nested_set/awesome_nested_set.rb, line 297 def left self[left_column_name] end
Find the first sibling to the left
# File lib/awesome_nested_set/awesome_nested_set.rb, line 404 def left_sibling siblings.where(["#{quoted_left_column_full_name} < ?", left]). order("#{quoted_left_column_full_name} DESC").last end
Returns the level of this object in the tree root level is 0
# File lib/awesome_nested_set/awesome_nested_set.rb, line 363 def level parent_id.nil? ? 0 : compute_level end
Shorthand method for finding the left sibling and moving to the left of it.
# File lib/awesome_nested_set/awesome_nested_set.rb, line 415 def move_left move_to_left_of left_sibling end
# File lib/awesome_nested_set/awesome_nested_set.rb, line 477 def move_possible?(target) self != target && # Can't target self same_scope?(target) && # can't be in different scopes # !(left..right).include?(target.left..target.right) # this needs tested more # detect impossible move !((left <= target.left && right >= target.left) or (left <= target.right && right >= target.right)) end
Shorthand method for finding the right sibling and moving to the right of it.
# File lib/awesome_nested_set/awesome_nested_set.rb, line 420 def move_right move_to_right_of right_sibling end
Move the node to the child of another node (you can pass id only)
# File lib/awesome_nested_set/awesome_nested_set.rb, line 435 def move_to_child_of(node) move_to node, :child end
Move the node to the child of another node with specify index (you can pass id only)
# File lib/awesome_nested_set/awesome_nested_set.rb, line 440 def move_to_child_with_index(node, index) if node.children.empty? move_to_child_of(node) elsif node.children.count == index move_to_right_of(node.children.last) else move_to_left_of(node.children[index]) end end
Move the node to the left of another node (you can pass id only)
# File lib/awesome_nested_set/awesome_nested_set.rb, line 425 def move_to_left_of(node) move_to node, :left end
Order children in a nested set by an attribute Can order by any attribute class that uses the Comparable mixin, for example a string or integer Usage example when sorting categories alphabetically: @new_category.move_to_ordered_child_of(@root, “name”)
# File lib/awesome_nested_set/awesome_nested_set.rb, line 458 def move_to_ordered_child_of(parent, order_attribute, ascending = true) self.move_to_root and return unless parent left = nil # This is needed, at least for the tests. parent.children.each do |n| # Find the node immediately to the left of this node. if ascending left = n if n.send(order_attribute) < self.send(order_attribute) else left = n if n.send(order_attribute) > self.send(order_attribute) end end self.move_to_child_of(parent) return unless parent.children.count > 1 # Only need to order if there are multiple children. if left # Self has a left neighbor. self.move_to_right_of(left) else # Self is the left most node. self.move_to_left_of(parent.children[0]) end end
Move the node to the left of another node (you can pass id only)
# File lib/awesome_nested_set/awesome_nested_set.rb, line 430 def move_to_right_of(node) move_to node, :right end
Move the node to root nodes
# File lib/awesome_nested_set/awesome_nested_set.rb, line 451 def move_to_root move_to nil, :root end
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.
category.self_and_descendants.count category.ancestors.find(:all, :conditions => "name like '%foo%'")
Value of the parent column
# File lib/awesome_nested_set/awesome_nested_set.rb, line 292 def parent_id self[parent_column_name] end
Value of the right column
# File lib/awesome_nested_set/awesome_nested_set.rb, line 302 def right self[right_column_name] end
Find the first sibling to the right
# File lib/awesome_nested_set/awesome_nested_set.rb, line 410 def right_sibling siblings.where(["#{quoted_left_column_full_name} > ?", left]).first end
Returns root
# File lib/awesome_nested_set/awesome_nested_set.rb, line 322 def root if persisted? self_and_ancestors.where(parent_column_name => nil).first else if parent_id && current_parent = nested_set_scope.find(parent_id) current_parent.root else self end end end
Returns true if this is a root node.
# File lib/awesome_nested_set/awesome_nested_set.rb, line 307 def root? parent_id.nil? end
Check if other model is in the same scope
# File lib/awesome_nested_set/awesome_nested_set.rb, line 397 def same_scope?(other) Array(acts_as_nested_set_options[:scope]).all? do |attr| self.send(attr) == other.send(attr) end end
Returns the array of all parents and self
# File lib/awesome_nested_set/awesome_nested_set.rb, line 335 def self_and_ancestors nested_set_scope.where([ "#{quoted_left_column_full_name} <= ? AND #{quoted_right_column_full_name} >= ?", left, right ]) end
Returns a set of itself and all of its nested children
# File lib/awesome_nested_set/awesome_nested_set.rb, line 368 def self_and_descendants nested_set_scope.where([ "#{quoted_left_column_full_name} >= ? AND #{quoted_left_column_full_name} < ?", left, right # using _left_ for both sides here lets us benefit from an index on that column if one exists ]) end
Returns the array of all children of the parent, including self
# File lib/awesome_nested_set/awesome_nested_set.rb, line 347 def self_and_siblings nested_set_scope.where(parent_column_name => parent_id) end
# File lib/awesome_nested_set/awesome_nested_set.rb, line 492 def compute_level node, nesting = self, 0 while (association = node.association(:parent)).loaded? && association.target nesting += 1 node = node.parent end if node.respond_to? :association node == self ? ancestors.count : node.level + nesting end
Prunes a branch off of the tree, shifting all of the elements on the right back to the left so the counts still work.
# File lib/awesome_nested_set/awesome_nested_set.rb, line 567 def destroy_descendants return if right.nil? || left.nil? || skip_before_destroy in_tenacious_transaction do reload_nested_set # select the rows in the model that extend past the deletion point and apply a lock nested_set_scope.where(["#{quoted_left_column_full_name} >= ?", left]). select(id).lock(true) if acts_as_nested_set_options[:dependent] == :destroy descendants.each do |model| model.skip_before_destroy = true model.destroy end else nested_set_scope.where(["#{quoted_left_column_name} > ? AND #{quoted_right_column_name} < ?", left, right]). delete_all end # update lefts and rights for remaining nodes diff = right - left + 1 nested_set_scope.where(["#{quoted_left_column_full_name} > ?", right]).update_all( ["#{quoted_left_column_name} = (#{quoted_left_column_name} - ?)", diff] ) nested_set_scope.where(["#{quoted_right_column_full_name} > ?", right]).update_all( ["#{quoted_right_column_name} = (#{quoted_right_column_name} - ?)", diff] ) # Don't allow multiple calls to destroy to corrupt the set self.skip_before_destroy = true end end
# File lib/awesome_nested_set/awesome_nested_set.rb, line 550 def in_tenacious_transaction(&block) retry_count = 0 begin transaction(&block) rescue ActiveRecord::StatementInvalid => error raise unless connection.open_transactions.zero? raise unless error.message =~ /Deadlock found when trying to get lock|Lock wait timeout exceeded/ raise unless retry_count < 10 retry_count += 1 logger.info "Deadlock detected on retry #{retry_count}, restarting transaction" sleep(rand(retry_count)*0.1) # Aloha protocol retry end end
# File lib/awesome_nested_set/awesome_nested_set.rb, line 609 def move_to(target, position) raise ActiveRecord::ActiveRecordError, "You cannot move a new node" if self.new_record? run_callbacks :move do in_tenacious_transaction do if target.is_a? self.class.base_class target.reload_nested_set elsif position != :root # load object if node is not an object target = nested_set_scope.find(target) end self.reload_nested_set unless position == :root || move_possible?(target) raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree." end bound = case position when :child; target[right_column_name] when :left; target[left_column_name] when :right; target[right_column_name] + 1 when :root; 1 else raise ActiveRecord::ActiveRecordError, "Position should be :child, :left, :right or :root ('#{position}' received)." end if bound > self[right_column_name] bound = bound - 1 other_bound = self[right_column_name] + 1 else other_bound = self[left_column_name] - 1 end # there would be no change return if bound == self[right_column_name] || bound == self[left_column_name] # we have defined the boundaries of two non-overlapping intervals, # so sorting puts both the intervals and their boundaries in order a, b, c, d = [self[left_column_name], self[right_column_name], bound, other_bound].sort # select the rows in the model between a and d, and apply a lock self.class.base_class.select('id').lock(true).where( ["#{quoted_left_column_full_name} >= :a and #{quoted_right_column_full_name} <= :d", {:a => a, :d => d}] ) new_parent = case position when :child; target.id when :root; nil else target[parent_column_name] end where_statement = ["not (#{quoted_left_column_name} = CASE " + "WHEN #{quoted_left_column_name} BETWEEN :a AND :b " + "THEN #{quoted_left_column_name} + :d - :b " + "WHEN #{quoted_left_column_name} BETWEEN :c AND :d " + "THEN #{quoted_left_column_name} + :a - :c " + "ELSE #{quoted_left_column_name} END AND " + "#{quoted_right_column_name} = CASE " + "WHEN #{quoted_right_column_name} BETWEEN :a AND :b " + "THEN #{quoted_right_column_name} + :d - :b " + "WHEN #{quoted_right_column_name} BETWEEN :c AND :d " + "THEN #{quoted_right_column_name} + :a - :c " + "ELSE #{quoted_right_column_name} END AND " + "#{quoted_parent_column_name} = CASE " + "WHEN #{self.class.base_class.primary_key} = :id THEN :new_parent " + "ELSE #{quoted_parent_column_name} END)" , {:a => a, :b => b, :c => c, :d => d, :id => self.id, :new_parent => new_parent} ] self.nested_set_scope.where(*where_statement).update_all([ "#{quoted_left_column_name} = CASE " + "WHEN #{quoted_left_column_name} BETWEEN :a AND :b " + "THEN #{quoted_left_column_name} + :d - :b " + "WHEN #{quoted_left_column_name} BETWEEN :c AND :d " + "THEN #{quoted_left_column_name} + :a - :c " + "ELSE #{quoted_left_column_name} END, " + "#{quoted_right_column_name} = CASE " + "WHEN #{quoted_right_column_name} BETWEEN :a AND :b " + "THEN #{quoted_right_column_name} + :d - :b " + "WHEN #{quoted_right_column_name} BETWEEN :c AND :d " + "THEN #{quoted_right_column_name} + :a - :c " + "ELSE #{quoted_right_column_name} END, " + "#{quoted_parent_column_name} = CASE " + "WHEN #{self.class.base_class.primary_key} = :id THEN :new_parent " + "ELSE #{quoted_parent_column_name} END", {:a => a, :b => b, :c => c, :d => d, :id => self.id, :new_parent => new_parent} ]) end target.reload_nested_set if target self.set_depth! self.descendants.each(&:save) self.reload_nested_set end end
# File lib/awesome_nested_set/awesome_nested_set.rb, line 522 def move_to_new_parent if @move_to_new_parent_id.nil? move_to_root elsif @move_to_new_parent_id move_to_child_of(@move_to_new_parent_id) end end
All nested set queries should use this nested_set_scope, which performs finds on the base ActiveRecord class, using the :scope declared in the acts_as_nested_set declaration.
# File lib/awesome_nested_set/awesome_nested_set.rb, line 508 def nested_set_scope(options = {}) options = {:order => quoted_left_column_full_name}.merge(options) scopes = Array(acts_as_nested_set_options[:scope]) options[:conditions] = scopes.inject({}) do |conditions,attr| conditions.merge attr => self[attr] end unless scopes.empty? self.class.base_class.unscoped.scoped options end
reload left, right, and parent
# File lib/awesome_nested_set/awesome_nested_set.rb, line 602 def reload_nested_set reload( :select => "#{quoted_left_column_full_name}, #{quoted_right_column_full_name}, #{quoted_parent_column_full_name}", :lock => true ) end
on creation, set automatically lft and rgt to the end of the tree
# File lib/awesome_nested_set/awesome_nested_set.rb, line 542 def set_default_left_and_right highest_right_row = nested_set_scope(:order => "#{quoted_right_column_full_name} desc").limit(1).lock(true).first maxright = highest_right_row ? (highest_right_row[right_column_name] || 0) : 0 # adds the new node to the right of all existing nodes self[left_column_name] = maxright + 1 self[right_column_name] = maxright + 2 end
# File lib/awesome_nested_set/awesome_nested_set.rb, line 530 def set_depth! if nested_set_scope.column_names.map(&:to_s).include?(depth_column_name.to_s) in_tenacious_transaction do reload nested_set_scope.where(:id => id).update_all(["#{quoted_depth_column_name} = ?", level]) end self[depth_column_name.to_sym] = self.level end end
Generated with the Darkfish Rdoc Generator 2.