acts_as_tree_on_steroids
acts_as_tree_on_steroids copied to clipboard
Extends acts_as_tree to allow single query selections on the tree
In a nutshell
Extend classic acts_as_tree, acts_as_nested_set to support:
- Fetching descendants with one sinqle query
- Fetching anscestors with one single query
- Fetching the root node of a node with one query
- Store the level of each node
- Store the children count of each node
- Configure a "family" based on the level (depth) that can be different from the root node
Why?
The problem with classic acts_as_tree is that you need a large number of queries to retrieve the ascendants or descendants of a node. If you want the path of a node with a level of N , you need N – 1 queries. If you want all the children and grandchildren and so forth , you need to recurse at least N times. This can become a pain if your tree is more than 3 or 4 levels. Acts as nested set offers functionality to retrieve all descendants of a node with a single query, but you can't do the same for ancestors which is very usefull for a number of things (i.e breadcrumbs)
If your application offers a hierarchical based browsing, you'll probably need to query the tree all the time to create breadcrumbs or lists with subcategories. Classic examples are ecommerce applications, price comparison sites etc.
Families:
In ecommerce applications it's very common to have families of products or categories. For example:
Electronics | -> Computers & Peripherals | --> Storage | ----> Hard Disks | --------> SSD
The root node of SSD is Electronics but that's not the actually family of products because it covers a really big range. The actual family (depending on the application logic) can be Computers & Peripherals or Storage. If family_level is set to 1 for example, the family will be Computers & Peripherals.
Installing
Install the plugin: script/plugin install http://github.com/bandito/acts_as_tree_on_steroids.git
Usage
Your model must have the following database tables:
- parent_id (lnteger)
- id_path (string)
- children_count (integer)
- level (integer)
Optionally you can enable family support by adding the following column.
- family_id (integer)
You should add indexes for id_path, parent_id and family_id (level and children_count will probably have a low cardinality , but you can index them anyway)
Then include the helper in your model.
class Category acts_as_tree_on_steroids :family_level => 1 end
How does it work
When a node is created or changes parent the id_path reflects the node path from the root to the node as a comma seperated value of ids. For example a really small tree would look like this
1 1,2 1,2,3 1,2,4 1,5 1,6 1,6,11 1,6,11,17 20 20,21 20,21,22 20,21,23 20,21,23,25
If we want to get the descendants of node with id_path 1,6 then we would query for "id_path like '1,6,%'" which would match 1,6,11 and 1,6,11,17. The database will use the index on id_path since it's not starting with a wildcard.
Likewise, if we wanted the ancestors of the node with id_path 1,6,11,17 we would just query for "id in (1,6,11,17)" to get them with a single query.
When to use it
- You have a category tree that rarely changes but you are doing breadcrumbs and tree representations all the time.
- You have to need to query for i.e products that belong to the current node and the descendant nodes
When not to use it
- If your tree changes frequently then the overhead of the tree traverse can be a pain.
- You don't need to know the hierarchy of the node.