| 1 |
= Better nested set |
|---|
| 2 |
|
|---|
| 3 |
This plugin provides an ehanced 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. |
|---|
| 4 |
|
|---|
| 5 |
== Details |
|---|
| 6 |
|
|---|
| 7 |
This acts provides Nested Set functionality. Nested Set is a smart way to implement |
|---|
| 8 |
an _ordered_ tree, with the added feature that you can select the children and all of their |
|---|
| 9 |
descendents with a single query. The drawback is that insertion or move need some complex |
|---|
| 10 |
sql queries. But everything is done behind the curtains by this module! |
|---|
| 11 |
|
|---|
| 12 |
Nested sets are appropriate each time you want either an orderd tree (menus, |
|---|
| 13 |
commercial categories) or an efficient way of querying big trees (threaded posts). |
|---|
| 14 |
|
|---|
| 15 |
See http://www.dbmsmag.com/9603d06.html for nested sets theory, and a tutorial here: |
|---|
| 16 |
http://threebit.net/tutorials/nestedset/tutorial1.html |
|---|
| 17 |
|
|---|
| 18 |
== Small nested set theory reminder |
|---|
| 19 |
|
|---|
| 20 |
Instead of picturing a leaf node structure with children pointing back to their parent, |
|---|
| 21 |
the best way to imagine how this works is to think of the parent entity surrounding all |
|---|
| 22 |
of its children, and its parent surrounding it, etc. Assuming that they are lined up |
|---|
| 23 |
horizontally, we store the left and right boundries in the database. |
|---|
| 24 |
|
|---|
| 25 |
Imagine: |
|---|
| 26 |
root |
|---|
| 27 |
|_ Child 1 |
|---|
| 28 |
|_ Child 1.1 |
|---|
| 29 |
|_ Child 1.2 |
|---|
| 30 |
|_ Child 2 |
|---|
| 31 |
|_ Child 2.1 |
|---|
| 32 |
|_ Child 2.2 |
|---|
| 33 |
|
|---|
| 34 |
If my cirlces in circles description didn't make sense, check out this sweet |
|---|
| 35 |
ASCII art: |
|---|
| 36 |
|
|---|
| 37 |
___________________________________________________________________ |
|---|
| 38 |
| Root | |
|---|
| 39 |
| ____________________________ ____________________________ | |
|---|
| 40 |
| | Child 1 | | Child 2 | | |
|---|
| 41 |
| | __________ _________ | | __________ _________ | | |
|---|
| 42 |
| | | C 1.1 | | C 1.2 | | | | C 2.1 | | C 2.2 | | | |
|---|
| 43 |
1 2 3_________4 5________6 7 8 9_________10 11_______12 13 14 |
|---|
| 44 |
| |___________________________| |___________________________| | |
|---|
| 45 |
|___________________________________________________________________| |
|---|
| 46 |
|
|---|
| 47 |
The numbers represent the left and right boundries. The table then might |
|---|
| 48 |
look like this: |
|---|
| 49 |
id | parent_is | left | right | data |
|---|
| 50 |
1 | | 1 | 14 | root |
|---|
| 51 |
2 | 1 | 2 | 7 | Child 1 |
|---|
| 52 |
3 | 2 | 3 | 4 | Child 1.1 |
|---|
| 53 |
4 | 2 | 5 | 6 | Child 1.2 |
|---|
| 54 |
5 | 1 | 8 | 13 | Child 2 |
|---|
| 55 |
6 | 5 | 9 | 10 | Child 2.1 |
|---|
| 56 |
7 | 5 | 11 | 12 | Child 2.2 |
|---|
| 57 |
|
|---|
| 58 |
So, to get all children of an entry 'parent', you |
|---|
| 59 |
SELECT * WHERE left IS BETWEEN parent.left AND parent.right |
|---|
| 60 |
|
|---|
| 61 |
To get the count, it's (right - left - 1)/2, etc. |
|---|
| 62 |
To get the direct parent, it falls back to using the parent_id field. |
|---|
| 63 |
There are instance methods for all of these. |
|---|
| 64 |
|
|---|
| 65 |
|
|---|
| 66 |
= API |
|---|
| 67 |
|
|---|
| 68 |
Methods names are aligned on Tree's ones as much as possible, to make replacment from one |
|---|
| 69 |
by another easier, except for the creation: |
|---|
| 70 |
|
|---|
| 71 |
in acts_as_tree: |
|---|
| 72 |
|
|---|
| 73 |
item.children.create(:name => "child1") |
|---|
| 74 |
|
|---|
| 75 |
in acts_as_nested_set: |
|---|
| 76 |
|
|---|
| 77 |
# adds a new item at the "end" of the tree, i.e. with child.left = max(tree.right)+1 |
|---|
| 78 |
child = MyClass.new(:name => "child1") |
|---|
| 79 |
child.save |
|---|
| 80 |
# now move the item to its right place |
|---|
| 81 |
child.move_to_child_of my_item |
|---|
| 82 |
|
|---|
| 83 |
You can use: |
|---|
| 84 |
* <tt>move_to_child_of</tt> |
|---|
| 85 |
* <tt>move_to_right_of</tt> |
|---|
| 86 |
* <tt>move_to_left_of</tt> |
|---|
| 87 |
and pass them an id or an object. |
|---|
| 88 |
|
|---|
| 89 |
Other methods added by this mixin are: |
|---|
| 90 |
* <tt>root</tt> - root item of the tree (the one that has a nil parent; should have left_column = 1 too) |
|---|
| 91 |
* <tt>roots</tt> - root items, in case of multiple roots (the ones that have a nil parent) |
|---|
| 92 |
* <tt>level</tt> - number indicating the level, a root being level 0 |
|---|
| 93 |
* <tt>ancestors</tt> - array of all parents, with root as first item |
|---|
| 94 |
* <tt>self_and_ancestors</tt> - array of all parents and self |
|---|
| 95 |
* <tt>siblings</tt> - array of all siblings, that are the items sharing the same parent and level |
|---|
| 96 |
* <tt>self_and_siblings</tt> - array of itself and all siblings |
|---|
| 97 |
* <tt>children_count</tt> - count of all immediate children |
|---|
| 98 |
* <tt>children</tt> - array of all immediate childrens |
|---|
| 99 |
* <tt>all_children</tt> - array of all children and nested children |
|---|
| 100 |
* <tt>full_set</tt> - array of itself and all children and nested children |
|---|
| 101 |
|
|---|
| 102 |
These should not be useful, except if you want to write direct SQL: |
|---|
| 103 |
* <tt>left_col_name</tt> - name of the left column passed on the declaration line |
|---|
| 104 |
* <tt>right_col_name</tt> - name of the right column passed on the declaration line |
|---|
| 105 |
* <tt>parent_col_name</tt> - name of the parent column passed on the declaration line |
|---|
| 106 |
|
|---|
| 107 |
== Recommandation |
|---|
| 108 |
|
|---|
| 109 |
Don't name your left and right columns 'left' and 'right': these names are reserved on most of dbs. |
|---|
| 110 |
Usage is to name them 'lft' and 'rgt' for instance. |
|---|
| 111 |
|
|---|
| 112 |
= Where to find better_nested_set |
|---|
| 113 |
|
|---|
| 114 |
This plugin is provided by Jean-Christophe Michel from Symétrie, and is available on |
|---|
| 115 |
|
|---|
| 116 |
http://opensource.symetrie.com/trac/better_nested_set/ |
|---|
| 117 |
|
|---|
| 118 |
Subversion repository is on |
|---|
| 119 |
|
|---|
| 120 |
http://opensource.symetrie.com/svn/better_nested_set/trunk |
|---|
| 121 |
|
|---|
| 122 |
= License |
|---|
| 123 |
|
|---|
| 124 |
Copyright (c) 2006 Jean-Christophe Michel, Symétrie |
|---|
| 125 |
|
|---|
| 126 |
The MIT License |
|---|
| 127 |
|
|---|
| 128 |
Permission is hereby granted, free of charge, to any person obtaining a copy |
|---|
| 129 |
of this software and associated documentation files (the "Software"), to deal |
|---|
| 130 |
in the Software without restriction, including without limitation the rights |
|---|
| 131 |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|---|
| 132 |
copies of the Software, and to permit persons to whom the Software is |
|---|
| 133 |
furnished to do so, subject to the following conditions: |
|---|
| 134 |
|
|---|
| 135 |
The above copyright notice and this permission notice shall be included in |
|---|
| 136 |
all copies or substantial portions of the Software. |
|---|
| 137 |
|
|---|
| 138 |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|---|
| 139 |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|---|
| 140 |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|---|
| 141 |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|---|
| 142 |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|---|
| 143 |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|---|
| 144 |
THE SOFTWARE |
|---|