|
| 1 | +# Closure Tree |
| 2 | + |
| 3 | +Closure Tree is a mostly-API-compatible replacement for the |
| 4 | +acts_as_tree and awesome_nested_set gems, but with much better |
| 5 | +mutation performance thanks to the Closure Tree storage algorithm. |
| 6 | + |
| 7 | +See [Bill Karwin](http://karwin.blogspot.com/)'s excellent |
| 8 | +[Models for hierarchical data presentation](http://www.slideshare.net/billkarwin/models-for-hierarchical-data) |
| 9 | +for a description of different tree storage algorithms. |
| 10 | + |
| 11 | +## Setup |
| 12 | + |
| 13 | +Note that closure_tree is being developed for Rails 3.1.0.rc1 |
| 14 | + |
| 15 | +1. Add this to your Gemfile: ```gem 'closure_tree'``` |
| 16 | + |
| 17 | +2. Run ```bundle install``` |
| 18 | + |
| 19 | +3. Add ```acts_as_tree``` to your hierarchical model(s). |
| 20 | + |
| 21 | +4. Add a migration to add a ```parent_id``` column to the model you want to act_as_tree. |
| 22 | + |
| 23 | + Note that if the column is null, the tag will be considered a root node. |
| 24 | + |
| 25 | + class AddParentIdToTag < ActiveRecord::Migration |
| 26 | + def change |
| 27 | + add_column :tag, :parent_id, :integer |
| 28 | + end |
| 29 | + end |
| 30 | + |
| 31 | +5. Add a database migration to store the hierarchy for your model. By |
| 32 | + convention the table name will be the model's table name, followed by |
| 33 | + "_hierarchy". Note that by calling ```acts_as_tree```, a "virtual model" (in this case, ```TagsHierarchy```) will be added automatically, so you don't need to create it. |
| 34 | + |
| 35 | + class CreateTagHierarchy < ActiveRecord::Migration |
| 36 | + def change |
| 37 | + create_table :tags_hierarchy, :id => false do |t| |
| 38 | + t.integer :ancestor_id, :null => false # ID of the parent/grandparent/great-grandparent/... tag |
| 39 | + t.integer :descendant_id, :null => false # ID of the target tag |
| 40 | + t.integer :generations, :null => false # Number of generations between the ancestor and the descendant. Parent/child = 1, for example. |
| 41 | + end |
| 42 | + |
| 43 | + # For "all progeny of..." selects: |
| 44 | + add_index :tags_hierarchy, [:ancestor_id, :descendant_id], :unique => true |
| 45 | + |
| 46 | + # For "all ancestors of..." selects |
| 47 | + add_index :tags_hierarchy, [:descendant_id] |
| 48 | + end |
| 49 | + end |
| 50 | + |
| 51 | +6. Run ```rake db:migrate``` |
| 52 | + |
| 53 | +7. If you're migrating away from another system where your model already has a |
| 54 | + ```parent_id``` column, run ```Tag.rebuild!``` and the |
| 55 | + ..._hierarchy table will be truncated and rebuilt. |
| 56 | + |
| 57 | + If you're starting from scratch you don't need to call ```rebuild!```. |
| 58 | + |
| 59 | +## Usage |
| 60 | + |
| 61 | +### Creation |
| 62 | + |
| 63 | +Create a root node: |
| 64 | + |
| 65 | + ```ruby |
| 66 | + grandparent = Tag.create!(:name => 'Grandparent') |
| 67 | + ``` |
| 68 | + |
| 69 | +There are two equivalent ways to add children. Either use the ```add_child``` method: |
| 70 | + |
| 71 | + ```ruby |
| 72 | + parent = Tag.create!(:name => 'Parent') |
| 73 | + grandparent.add_child parent |
| 74 | + ``` |
| 75 | + |
| 76 | +Or append to the ```children``` collection: |
| 77 | + |
| 78 | + ```ruby |
| 79 | + child = Tag.create!(:name => 'Child') |
| 80 | + parent.children << child |
| 81 | + ``` |
| 82 | + |
| 83 | +Then: |
| 84 | + |
| 85 | + ```ruby |
| 86 | + puts grandparent.self_and_descendants.collect{ |t| t.name }.join(" > ") |
| 87 | + "grandparent > parent > child" |
| 88 | + |
| 89 | + child.ancestry_path |
| 90 | + ["grandparent", "parent", "child"] |
| 91 | + ``` |
| 92 | + |
| 93 | +### ```find_or_create_by_path``` |
| 94 | + |
| 95 | +We can do all the node creation and add_child calls from the prior section with one method call: |
| 96 | + child = Tag.find_or_create_by_path "grandparent", "parent", "child" |
| 97 | + |
| 98 | +You can ```find``` as well as ```find_or_create``` by "ancestry paths". Ancestry paths may be built using any column in your model. The default column is ```name```, which can be changed with the :name_column option provided to ```acts_as_tree```. |
| 99 | + |
| 100 | +Note that the other columns will be null if nodes are created, other than auto-generated columns like ID and created_at timestamp. Only the specified column will receive the path element value. |
| 101 | + |
| 102 | +## Accessing Data |
| 103 | + |
| 104 | +### Class methods |
| 105 | + |
| 106 | +* ``` Tag.root``` returns an arbitrary root node |
| 107 | +* ``` Tag.roots``` returns all root nodes |
| 108 | +* ``` Tag.leaves``` returns all leaf nodes |
| 109 | + |
| 110 | +### Instance methods |
| 111 | + |
| 112 | +* ``` tag.root``` returns the root for this node |
| 113 | +* ``` tag.root?``` returns true if this is a root node |
| 114 | +* ``` tag.child?``` returns true if this is a child node. It has a parent. |
| 115 | +* ``` tag.leaf?``` returns true if this is a leaf node. It has no children. |
| 116 | +* ``` tag.leaves``` returns an array of all the nodes in self_and_descendants that are leaves. |
| 117 | +* ``` tag.level``` returns the level, or "generation", for this node in the tree. A root node = 0 |
| 118 | +* ``` tag.parent``` returns the node's immediate parent |
| 119 | +* ``` tag.children``` returns an array of immediate children (just those in the next level). |
| 120 | +* ``` tag.ancestors``` returns an array of all parents, parents' parents, etc, excluding self. |
| 121 | +* ``` tag.self_and_ancestors``` returns an array of all parents, parents' parents, etc, including self. |
| 122 | +* ``` tag.siblings``` returns an array of brothers and sisters (all at that level), excluding self. |
| 123 | +* ``` tag.self_and_siblings``` returns an array of brothers and sisters (all at that level), including self. |
| 124 | +* ``` tag.descendants``` returns an array of all children, childrens' children, etc., excluding self. |
| 125 | +* ``` tag.self_and_descendants``` returns an array of all children, childrens' children, etc., including self. |
| 126 | + |
| 127 | +## Thanks to |
| 128 | + |
| 129 | +* https://github.com/collectiveidea/awesome_nested_set |
| 130 | +* https://github.com/patshaughnessy/class_factory |
0 commit comments