BetterNestedSet

From WhyNotWiki
Jump to: navigation, search
Documentation: .
Source code: http://opensource.symetrie.com/svn/better_nested_set/trunk
Project/Development: http://opensource.symetrie.com/trac/better_nested_set/
As listed in other directories: http://www.agilewebdevelopment.com/plugins/betternestedset


Based on: ActsAsNestedSet
License: MIT


Authors: Jean-Christophe Michel


http://opensource.symetrie.com/svn/better_nested_set/trunk/README

This plugin provides an enhanced acts_as_nested_set mixin for ActiveRecord, the object-db mapping layer of the framework RubyOnRails. The original nested set feature seems to be quite old and missed some necessary functionalities. Provides Nested Set functionality. Nested Set is a smart way to implement an ordered tree, with the added feature that you can select the children and all of their descendants with a single query. Nested sets are appropriate each time you want either an ordered tree (menus, commercial categories) or an efficient way of querying big trees (threaded posts). == Small nested set theory reminder Instead of picturing a leaf node structure with children pointing back to their parent, the best way to imagine how this works is to think of the parent entity surrounding all of its children, and its parent surrounding it, etc. Assuming that they are lined up horizontally, we store the left and right boundries in the database. Imagine:

  root
    |_ Child 1
      |_ Child 1.1
      |_ Child 1.2
    |_ Child 2
      |_ Child 2.1
      |_ Child 2.2

If my cirlces in circles description didn't make sense, check out this sweet ASCII art:

    ___________________________________________________________________
   |  Root                                                             |
   |    ____________________________    ____________________________   |
   |   |  Child 1                  |   |  Child 2                  |   |
   |   |   __________   _________  |   |   __________   _________  |   |
   |   |  |  C 1.1  |  |  C 1.2 |  |   |  |  C 2.1  |  |  C 2.2 |  |   |
   1   2  3_________4  5________6  7   8  9_________10 11_______12 13  14
   |   |___________________________|   |___________________________|   |
   |___________________________________________________________________|

The numbers represent the left and right boundries. The table then might look like this:

   id | parent_is | left | right | data
    1 |           |    1 |    14 | root
    2 |         1 |    2 |     7 | Child 1
    3 |         2 |    3 |     4 | Child 1.1
    4 |         2 |    5 |     6 | Child 1.2
    5 |         1 |    8 |    13 | Child 2
    6 |         5 |    9 |    10 | Child 2.1
    7 |         5 |   11 |    12 | Child 2.2

So, to get all children of an entry 'parent', you

    SELECT * WHERE left IS BETWEEN parent.left AND parent.right

To get the count, it's (right - left - 1)/2, etc. To get the direct parent, it falls back to using the parent_id field. There are instance methods for all of these.


Contents

API

Methods names are aligned on Tree's ones as much as possible, to make replacement from one by another easier, except for the creation:

in acts_as_tree:

  item.children.create(:name => "child1")

in acts_as_nested_set:

  # adds a new item at the "end" of the tree, i.e. with child.left = max(tree.right)+1
  child = MyClass.new(:name => "child1")
  child.save
  # now move the item to its right place
  child.move_to_child_of my_item

You can use:

  • move_to_child_of
  • move_to_right_of
  • move_to_left_of

and pass them an id or an object.

Other methods added by this mixin are:

  • root - root item of the tree (the one that has a nil parent; should have left_column = 1 too)
  • roots - root items, in case of multiple roots (the ones that have a nil parent)
  • level - number indicating the level, a root being level 0
  • ancestors - array of all parents, with root as first item
  • self_and_ancestors - array of all parents and self
  • siblings - array of all siblings, that are the items sharing the same parent and level
  • self_and_siblings - array of itself and all siblings
  • children_count - count of all immediate children
  • children - array of all immediate childrens
  • all_children - array of all children and nested children
  • full_set - array of itself and all children and nested children

These should not be useful, except if you want to write direct SQL:

  • left_col_name - name of the left column passed on the declaration line
  • right_col_name - name of the right column passed on the declaration line
  • parent_col_name - name of the parent column passed on the declaration line



Dissection: move_to_child_of()

vendor/plugins/better_nested_set/lib/better_nested_set.rb

        def move_to_child_of(node)
            self.move_to node, :child
        end

        def move_to(target, position)
          ...

          # compute new left/right for self
          if position == :child
            if target_left < cur_left
              new_left  = target_left + 1
              new_right = target_left + extent
            else
              new_left  = target_left - extent + 1
              new_right = target_left
            end

target is the node that you want to become the new parent of self.

Note that self will become the left-most node of target (new_left = target_left + 1).

I would have preferred that it become the right-most node of target (new_right = target_right - 1), or at least to have the option as to which side it should be inserted on, but currently I don't yet have the confidence to make that change.

Questions

Has there been any effort to get this included in core Rails?

If it's better than the core ActsAsNestedSet, then this should replace it and become the new ActsAsNestedSet?

Has anyone documented the exact differences between ActsAsNestedSet and BetterNestedSet?

And if so, have they tracked any changes...? (Or do I have to do that here...?)

For instance, perhaps Core has added all the features that BetterNestedSet has, rendering BetterNestedSet no longer useful...?



[ActiveRecord / Single table inheritance (category)] and trees

Bugs

http://dev.rubyonrails.org/ticket/7775

  1. 7775 (Derived class types aren't kept when using STI for acts_as_tree) - Rails Trac - Trac


When you create a base class with the acts_as_tree association in it, the class type isn't kept with the children.

class Shape < ActiveRecord::Base
    acts_as_tree
end

class Circle < Shape end

circles = Circle.create!
circles.children.create!

puts circles.class
circles.children.each {|c| puts c.class}

The first will print 'Circle' and the children will print 'Shape'. In my opinion, when a child is created it should look to it's parent to see what it's type should be. Therefore, all nodes in the tree are of the same type.

Ads
Personal tools