Module: SymetrieCom::Acts::NestedSet::InstanceMethods
- Defined in:
- lib/better_nested_set.rb
Overview
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.
Instance Method Summary collapse
-
#<=>(x) ⇒ Object
By default, records are compared and sorted using the left column.
-
#add_child(child) ⇒ Object
Deprecated.
-
#all_children(scope = {}) ⇒ Object
Returns all children and nested children.
-
#all_children_count(scope = nil) ⇒ Object
Returns the number of nested children of this object.
-
#all_children_through(other, scope = {}) ⇒ Object
All children until the other is reached - excluding self.
-
#ancestors(scope = {}) ⇒ Object
Returns an array of all parents, starting with the root.
-
#ancestors_and_self_through(other, scope = {}) ⇒ Object
All nodes between two nodes, those nodes included in effect all ancestors until the other is reached.
-
#ancestors_through(other, scope = {}) ⇒ Object
Ancestors until the other is reached - excluding self.
-
#base_set_class ⇒ Object
:nodoc:.
-
#check_full_tree ⇒ Object
Checks the left/right indexes of the entire tree that this node belongs to, returning the number of records checked.
-
#check_subtree ⇒ Object
Checks the left/right indexes of one node and all descendants.
-
#child? ⇒ Boolean
Deprecated.
-
#child_by_id(id, scope = {}) ⇒ Object
Returns the child for the requested id within the scope of its children, otherwise nil.
-
#child_of?(parent, scope = {}) ⇒ Boolean
Tests wether self is within scope of parent.
-
#children(scope = {}) ⇒ Object
(also: #direct_children)
Returns this record’s immediate children.
- #children?(scope = {}) ⇒ Boolean
-
#children_by_id(*args) ⇒ Object
Returns a child collection for the requested ids within the scope of its children, otherwise empty array.
- #children_count(scope = {}) ⇒ Object
-
#destroy_descendants ⇒ Object
On destruction, delete all children and shift the lft/rgt values back to the left so the counts still work.
-
#direct_child_by_id(id, scope = {}) ⇒ Object
Returns the child for the requested id within the scope of its immediate children, otherwise nil.
-
#direct_child_of?(parent, scope = {}) ⇒ Boolean
Tests wether self is within immediate scope of parent.
-
#direct_children_by_id(*args) ⇒ Object
Returns a child collection for the requested ids within the scope of its immediate children, otherwise empty array.
-
#first_sibling(scope = {}) ⇒ Object
Returns first siblings amongst it’s siblings.
- #first_sibling?(scope = {}) ⇒ Boolean (also: #first?)
-
#full_set(scope = {}) ⇒ Object
Returns itself and all nested children.
-
#full_set_through(other, scope = {}) ⇒ Object
All children until the other is reached - including self.
-
#insert_at(target, index = :last, scope = {}) ⇒ Object
Insert a node at a specific position among the children of target.
-
#last_sibling(scope = {}) ⇒ Object
Returns last siblings amongst it’s siblings.
- #last_sibling?(scope = {}) ⇒ Boolean (also: #last?)
-
#leaves(scope = {}) ⇒ Object
Returns this record’s terminal children (nodes without children).
-
#leaves_count(scope = {}) ⇒ Object
Returns the count of this record’s terminal children (nodes without children).
-
#left_col_name ⇒ Object
convenience methods to make the code more readable.
-
#level(scope = {}) ⇒ Object
Returns the level of this object in the tree, root level being 0.
-
#move_higher ⇒ Object
Moves a node one down amongst its siblings.
-
#move_lower ⇒ Object
Moves a node one up amongst its siblings.
-
#move_to_bottom ⇒ Object
Moves a node one to be the last amongst its siblings.
-
#move_to_child_of(target) ⇒ Object
Make this node a child of target (you can pass an object or just an id).
-
#move_to_left_of(target) ⇒ Object
Move this node to the left of target (you can pass an object or just an id).
-
#move_to_position(index, scope = {}) ⇒ Object
Moves a node to a certain position amongst its siblings.
-
#move_to_right_of(target) ⇒ Object
Move this node to the right of target (you can pass an object or just an id).
-
#move_to_top ⇒ Object
Moves a node one to be the first amongst its siblings.
-
#next_sibling(num = 1, scope = {}) ⇒ Object
(also: #lower_item)
Returns next sibling of node or nil if there is none.
-
#next_siblings(scope = {}) ⇒ Object
Returns all siblings to the right of self, in ascending order, so the first sibling is the one closest to the right of self.
-
#parent ⇒ Object
Returns this record’s parent.
-
#parent_col_name ⇒ Object
(also: #parent_column)
:nodoc:.
-
#prefixed_left_col_name ⇒ Object
:nodoc:.
-
#prefixed_parent_col_name ⇒ Object
:nodoc:.
-
#prefixed_right_col_name ⇒ Object
:nodoc:.
-
#previous_sibling(num = 1, scope = {}) ⇒ Object
(also: #higher_item)
Returns previous sibling of node or nil if there is none.
-
#previous_siblings(scope = {}) ⇒ Object
Returns all siblings to the left of self, in descending order, so the first sibling is the one closest to the left of self.
-
#renumber_full_tree ⇒ Object
Re-calculate the left/right values of all nodes in this record’s tree.
-
#reorder_children(*ids) ⇒ Object
Reorder children according to an array of ids.
-
#right_col_name ⇒ Object
:nodoc:.
-
#root(scope = {}) ⇒ Object
Returns this record’s root ancestor.
-
#root? ⇒ Boolean
Deprecated.
-
#roots(scope = {}) ⇒ Object
Returns the root or virtual roots of this record’s tree (a tree cannot have more than one real root).
-
#self_and_ancestors(scope = {}) ⇒ Object
Returns an array of all parents plus self, starting with the root.
-
#self_and_siblings(scope = {}) ⇒ Object
Returns all the children of this node’s parent, including self.
-
#self_and_siblings_through(other, scope = {}) ⇒ Object
All siblings until the other is reached - including self.
-
#set_left_right ⇒ Object
On creation, automatically add the new node to the right of all existing nodes in this tree.
-
#sibling_condition ⇒ Object
This takes care of valid queries when called on a root node.
-
#siblings(scope = {}) ⇒ Object
Returns all the children of this node’s parent, except self.
-
#siblings_through(other, scope = {}) ⇒ Object
All siblings until the other is reached - excluding self.
-
#swap(target, transact = true) ⇒ Object
Swaps the position of two sibling nodes preserving a sibling’s descendants.
-
#unknown? ⇒ Boolean
Deprecated.
Instance Method Details
#<=>(x) ⇒ Object
By default, records are compared and sorted using the left column.
450 451 452 |
# File 'lib/better_nested_set.rb', line 450 def <=>(x) self[left_col_name] <=> x[left_col_name] end |
#add_child(child) ⇒ Object
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.
754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 |
# File 'lib/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 = {}) ⇒ Object
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.
634 635 636 |
# File 'lib/better_nested_set.rb', line 634 def all_children(scope = {}) full_set(scope) - [self] end |
#all_children_count(scope = nil) ⇒ Object
Returns the number of nested children of this object.
567 568 569 570 |
# File 'lib/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 = {}) ⇒ Object
All children until the other is reached - excluding self
679 680 681 |
# File 'lib/better_nested_set.rb', line 679 def all_children_through(other, scope = {}) full_set_through(other, scope) - [self] end |
#ancestors(scope = {}) ⇒ Object
Returns an array of all parents, starting with the root.
489 490 491 |
# File 'lib/better_nested_set.rb', line 489 def ancestors(scope = {}) self_and_ancestors(scope) - [self] end |
#ancestors_and_self_through(other, scope = {}) ⇒ Object
All nodes between two nodes, those nodes included in effect all ancestors until the other is reached
667 668 669 670 671 |
# File 'lib/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 = {}) ⇒ Object
Ancestors until the other is reached - excluding self
674 675 676 |
# File 'lib/better_nested_set.rb', line 674 def ancestors_through(other, scope = {}) ancestors_and_self_through(other, scope) - [self] end |
#base_set_class ⇒ Object
:nodoc:
412 413 414 |
# File 'lib/better_nested_set.rb', line 412 def base_set_class#:nodoc: [:class] # for single-table inheritance end |
#check_full_tree ⇒ Object
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.
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 |
# File 'lib/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 ⇒ Object
Checks the left/right indexes of one node and all descendants. Throws ActiveRecord::ActiveRecordError if it finds a problem.
707 708 709 710 711 712 |
# File 'lib/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? ⇒ Boolean
Deprecated. Returns true if this is a child node
461 462 463 464 |
# File 'lib/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 = {}) ⇒ Object
Returns the child for the requested id within the scope of its children, otherwise nil
587 588 589 |
# File 'lib/better_nested_set.rb', line 587 def child_by_id(id, scope = {}) children_by_id(id, scope).first end |
#child_of?(parent, scope = {}) ⇒ Boolean
Tests wether self is within scope of parent
615 616 617 618 619 620 621 |
# File 'lib/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 = {}) ⇒ Object Also known as: direct_children
Returns this record’s immediate children.
643 644 645 |
# File 'lib/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 |
#children?(scope = {}) ⇒ Boolean
647 648 649 |
# File 'lib/better_nested_set.rb', line 647 def children?(scope = {}) children_count(scope) > 0 end |
#children_by_id(*args) ⇒ Object
Returns a child collection for the requested ids within the scope of its children, otherwise empty array
592 593 594 595 596 597 598 |
# File 'lib/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 = {}) ⇒ Object
638 639 640 |
# File 'lib/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 ⇒ Object
On destruction, delete all children and shift the lft/rgt values back to the left so the counts still work.
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 |
# File 'lib/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 [: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 = {}) ⇒ Object
Returns the child for the requested id within the scope of its immediate children, otherwise nil
601 602 603 |
# File 'lib/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 = {}) ⇒ Boolean
Tests wether self is within immediate scope of parent
624 625 626 627 628 629 630 |
# File 'lib/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_by_id(*args) ⇒ Object
Returns a child collection for the requested ids within the scope of its immediate children, otherwise empty array
606 607 608 609 610 611 612 |
# File 'lib/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_sibling(scope = {}) ⇒ Object
Returns first siblings amongst it’s siblings.
516 517 518 |
# File 'lib/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 = {}) ⇒ Boolean Also known as: first?
520 521 522 |
# File 'lib/better_nested_set.rb', line 520 def first_sibling?(scope = {}) self == first_sibling(scope) end |
#full_set(scope = {}) ⇒ Object
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.
574 575 576 577 578 579 580 581 582 583 584 |
# File 'lib/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 = {}) ⇒ Object
All children until the other is reached - including self
684 685 686 687 688 |
# File 'lib/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 |
#insert_at(target, index = :last, scope = {}) ⇒ Object
Insert a node at a specific position among the children of target.
830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 |
# File 'lib/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_sibling(scope = {}) ⇒ Object
Returns last siblings amongst it’s siblings.
526 527 528 |
# File 'lib/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 = {}) ⇒ Boolean Also known as: last?
530 531 532 |
# File 'lib/better_nested_set.rb', line 530 def last_sibling?(scope = {}) self == last_sibling(scope) end |
#leaves(scope = {}) ⇒ Object
Returns this record’s terminal children (nodes without children).
655 656 657 658 |
# File 'lib/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 = {}) ⇒ Object
Returns the count of this record’s terminal children (nodes without children).
661 662 663 |
# File 'lib/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 |
#left_col_name ⇒ Object
convenience methods to make the code more readable
393 394 395 |
# File 'lib/better_nested_set.rb', line 393 def left_col_name#:nodoc: self.class.left_col_name end |
#level(scope = {}) ⇒ Object
Returns the level of this object in the tree, root level being 0.
561 562 563 564 |
# File 'lib/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 |
#move_higher ⇒ Object
Moves a node one down amongst its siblings. Does nothing if it’s already the last sibling.
887 888 889 890 |
# File 'lib/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 ⇒ Object
Moves a node one up amongst its siblings. Does nothing if it’s already the first sibling.
880 881 882 883 |
# File 'lib/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 ⇒ Object
Moves a node one to be the last amongst its siblings. Does nothing if it’s already the last sibling.
901 902 903 904 |
# File 'lib/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) ⇒ Object
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.
869 870 871 |
# File 'lib/better_nested_set.rb', line 869 def move_to_child_of(target) self.move_to target, :child end |
#move_to_left_of(target) ⇒ Object
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.
857 858 859 |
# File 'lib/better_nested_set.rb', line 857 def move_to_left_of(target) self.move_to target, :left end |
#move_to_position(index, scope = {}) ⇒ Object
Moves a node to a certain position amongst its siblings.
874 875 876 |
# File 'lib/better_nested_set.rb', line 874 def move_to_position(index, scope = {}) insert_at(self.parent, index, scope) end |
#move_to_right_of(target) ⇒ Object
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.
863 864 865 |
# File 'lib/better_nested_set.rb', line 863 def move_to_right_of(target) self.move_to target, :right end |
#move_to_top ⇒ Object
Moves a node one to be the first amongst its siblings. Does nothing if it’s already the first sibling.
894 895 896 897 |
# File 'lib/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 = {}) ⇒ Object Also known as: lower_item
Returns next sibling of node or nil if there is none.
544 545 546 547 548 |
# File 'lib/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 |
#next_siblings(scope = {}) ⇒ Object
Returns all siblings to the right of self, in ascending order, so the first sibling is the one closest to the right of self
510 511 512 513 |
# File 'lib/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 ⇒ Object
Returns this record’s parent.
484 485 486 |
# File 'lib/better_nested_set.rb', line 484 def parent self.class.find_in_nested_set(self[parent_col_name]) if self[parent_col_name] end |
#parent_col_name ⇒ Object Also known as: parent_column
:nodoc:
405 406 407 |
# File 'lib/better_nested_set.rb', line 405 def parent_col_name#:nodoc: self.class.parent_col_name end |
#prefixed_left_col_name ⇒ Object
:nodoc:
396 397 398 |
# File 'lib/better_nested_set.rb', line 396 def prefixed_left_col_name#:nodoc: self.class.prefixed_left_col_name end |
#prefixed_parent_col_name ⇒ Object
:nodoc:
408 409 410 |
# File 'lib/better_nested_set.rb', line 408 def prefixed_parent_col_name#:nodoc: self.class.prefixed_parent_col_name end |
#prefixed_right_col_name ⇒ Object
:nodoc:
402 403 404 |
# File 'lib/better_nested_set.rb', line 402 def prefixed_right_col_name#:nodoc: self.class.prefixed_right_col_name end |
#previous_sibling(num = 1, scope = {}) ⇒ Object Also known as: higher_item
Returns previous sibling of node or nil if there is none.
536 537 538 539 540 |
# File 'lib/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 |
#previous_siblings(scope = {}) ⇒ Object
Returns all siblings to the left of self, in descending order, so the first sibling is the one closest to the left of self
504 505 506 507 |
# File 'lib/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 ⇒ Object
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.
736 737 738 739 740 741 742 743 744 745 746 747 748 |
# File 'lib/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) ⇒ Object
Reorder children according to an array of ids
913 914 915 916 917 918 919 920 921 922 |
# File 'lib/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 |
#right_col_name ⇒ Object
:nodoc:
399 400 401 |
# File 'lib/better_nested_set.rb', line 399 def right_col_name#:nodoc: self.class.right_col_name end |
#root(scope = {}) ⇒ Object
Returns this record’s root ancestor.
472 473 474 475 476 |
# File 'lib/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? ⇒ Boolean
Deprecated. Returns true if this is a root node.
455 456 457 458 |
# File 'lib/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 = {}) ⇒ Object
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.
479 480 481 |
# File 'lib/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 = {}) ⇒ Object
Returns an array of all parents plus self, starting with the root.
494 495 496 |
# File 'lib/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 = {}) ⇒ Object
Returns all the children of this node’s parent, including self.
552 553 554 555 556 557 558 |
# File 'lib/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 = {}) ⇒ Object
All siblings until the other is reached - including self
691 692 693 694 695 696 697 698 |
# File 'lib/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 ⇒ Object
On creation, automatically add the new node to the right of all existing nodes in this tree.
422 423 424 425 426 |
# File 'lib/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 ⇒ Object
This takes care of valid queries when called on a root node
417 418 419 |
# File 'lib/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 = {}) ⇒ Object
Returns all the children of this node’s parent, except self.
499 500 501 |
# File 'lib/better_nested_set.rb', line 499 def siblings(scope = {}) self_and_siblings(scope) - [self] end |
#siblings_through(other, scope = {}) ⇒ Object
All siblings until the other is reached - excluding self
701 702 703 |
# File 'lib/better_nested_set.rb', line 701 def siblings_through(other, scope = {}) self_and_siblings_through(other, scope) - [self] end |
#swap(target, transact = true) ⇒ Object
Swaps the position of two sibling nodes preserving a sibling’s descendants. The current implementation only works amongst siblings.
908 909 910 |
# File 'lib/better_nested_set.rb', line 908 def swap(target, transact = true) move_to(target, :swap, transact) end |
#unknown? ⇒ Boolean
Deprecated. Returns true if we have no idea what this is
467 468 469 |
# File 'lib/better_nested_set.rb', line 467 def unknown? !root? && !child? end |