# Closure Tree ### Closure_tree lets your ActiveRecord models act as nodes in a [tree data structure](http://en.wikipedia.org/wiki/Tree_%28data_structure%29) Common applications include modeling hierarchical data, like tags, threaded comments, page graphs in CMSes, and tracking user referrals. [![Build Status](https://secure.travis-ci.org/mceachen/closure_tree.png?branch=master)](http://travis-ci.org/mceachen/closure_tree) [![Gem Version](https://badge.fury.io/rb/closure_tree.png)](http://rubygems.org/gems/closure_tree) [![Code Climate](https://codeclimate.com/github/mceachen/closure_tree.png)](https://codeclimate.com/github/mceachen/closure_tree) [![Dependency Status](https://gemnasium.com/mceachen/closure_tree.png)](https://gemnasium.com/mceachen/closure_tree) Dramatically more performant than [ancestry](https://github.com/stefankroes/ancestry) and [acts_as_tree](https://github.com/amerine/acts_as_tree), and even more awesome than [awesome_nested_set](https://github.com/collectiveidea/awesome_nested_set/), closure_tree has some great features: * __Best-in-class select performance__: * Fetch your whole ancestor lineage in 1 SELECT. * Grab all your descendants in 1 SELECT. * Get all your siblings in 1 SELECT. * Fetch all [descendants as a nested hash](#nested-hashes) in 1 SELECT. * [Find a node by ancestry path](#find_or_create_by_path) in 1 SELECT. * __Best-in-class mutation performance__: * 2 SQL INSERTs on node creation * 3 SQL INSERT/UPDATEs on node reparenting * __Support for [concurrency](#concurrency)__ (using [with_advisory_lock](https://github.com/mceachen/with_advisory_lock)) * __Support for ActiveRecord 4.1, 4.2 and 5.0__ * __Support for Ruby 2.0, 2.1, 2.2, 2.3.1 and JRuby 9000__ * Support for reparenting children (and all their descendants) * Support for [single-table inheritance (STI)](#sti) within the hierarchy * ```find_or_create_by_path``` for [building out heterogeneous hierarchies quickly and conveniently](#find_or_create_by_path) * Support for [deterministic ordering](#deterministic-ordering) * Support for [preordered](http://en.wikipedia.org/wiki/Tree_traversal#Pre-order) traversal of descendants * Support for rendering trees in [DOT format](http://en.wikipedia.org/wiki/DOT_(graph_description_language)), using [Graphviz](http://www.graphviz.org/) * Excellent [test coverage](#testing) in a comprehensive variety of environments See [Bill Karwin](http://karwin.blogspot.com/)'s excellent [Models for hierarchical data presentation](http://www.slideshare.net/billkarwin/models-for-hierarchical-data) for a description of different tree storage algorithms. ## Table of Contents - [Installation](#installation) - [Warning](#warning) - [Usage](#usage) - [Accessing Data](#accessing-data) - [Polymorphic hierarchies with STI](#polymorphic-hierarchies-with-sti) - [Deterministic ordering](#deterministic-ordering) - [Concurrency](#concurrency) - [FAQ](#faq) - [Testing](#testing) - [Change log](#change-log) ## Installation Note that closure_tree only supports ActiveRecord 4.1 and later, and has test coverage for MySQL, PostgreSQL, and SQLite. 1. Add `gem 'closure_tree'` to your Gemfile 2. Run `bundle install` 3. Add `has_closure_tree` (or `acts_as_tree`, which is an alias of the same method) to your hierarchical model: ```ruby class Tag < ActiveRecord::Base has_closure_tree end class AnotherTag < ActiveRecord::Base acts_as_tree end ``` Make sure you check out the [large number options](#available-options) that `has_closure_tree` accepts. **IMPORTANT: Make sure you add `has_closure_tree` _after_ `attr_accessible` and `self.table_name =` lines in your model.** If you're already using other hierarchical gems, like `ancestry` or `acts_as_tree`, please refer to the [warning section](#warning)! 4. Add a migration to add a `parent_id` column to the hierarchical model. You may want to also [add a column for deterministic ordering of children](#deterministic-ordering), but that's optional. ```ruby class AddParentIdToTag < ActiveRecord::Migration def change add_column :tag, :parent_id, :integer end end ``` The column must be nullable. Root nodes have a `NULL` `parent_id`. 5. Run `rails g closure_tree:migration tag` (and replace `tag` with your model name) to create the closure tree table for your model. By default the table name will be the model's table name, followed by "_hierarchies". Note that by calling ```has_closure_tree```, a "virtual model" (in this case, ```TagHierarchy```) will be created dynamically. You don't need to create it. 6. Run `rake db:migrate` 7. If you're migrating from another system where your model already has a `parent_id` column, run `Tag.rebuild!` and your `tag_hierarchies` table will be truncated and rebuilt. If you're starting from scratch you don't need to call `rebuild!`. NOTE: Run `rails g closure_tree:config` to create an initializer with extra configurations. (Optional) ## Warning As stated above, using multiple hierarchy gems (like `ancestry` or `nested set`) on the same model will most likely result in pain, suffering, hair loss, tooth decay, heel-related ailments, and gingivitis. Assume things will break. ## Usage ### Creation Create a root node: ```ruby grandparent = Tag.create(name: 'Grandparent') ``` Child nodes are created by appending to the children collection: ```ruby parent = grandparent.children.create(name: 'Parent') ``` Or by appending to the children collection: ```ruby child2 = Tag.new(name: 'Second Child') parent.children << child2 ``` Or by calling the "add_child" method: ```ruby child3 = Tag.new(name: 'Third Child') parent.add_child child3 ``` Or by setting the parent on the child : ```ruby Tag.create(name: 'Fourth Child', parent: parent) ``` Then: ```ruby grandparent.self_and_descendants.collect(&:name) => ["Grandparent", "Parent", "First Child", "Second Child", "Third Child", "Fourth Child"] child1.ancestry_path => ["Grandparent", "Parent", "First Child"] ``` ### find_or_create_by_path You can `find` as well as `find_or_create` by "ancestry paths". If you provide an array of strings to these methods, they reference the `name` column in your model, which can be overridden with the `:name_column` option provided to `has_closure_tree`. ```ruby child = Tag.find_or_create_by_path(%w[grandparent parent child]) ``` As of v5.0.0, `find_or_create_by_path` can also take an array of attribute hashes: ```ruby child = Tag.find_or_create_by_path([ {name: 'Grandparent', title: 'Sr.'}, {name: 'Parent', title: 'Mrs.'}, {name: 'Child', title: 'Jr.'} ]) ``` If you're using STI, The attribute hashes can contain the `sti_name` and things work as expected: ```ruby child = Label.find_or_create_by_path([ {type: 'DateLabel', name: '2014'}, {type: 'DateLabel', name: 'August'}, {type: 'DateLabel', name: '5'}, {type: 'EventLabel', name: 'Visit the Getty Center'} ]) ``` ### Moving nodes around the tree Nodes can be moved around to other parents, and closure_tree moves the node's descendancy to the new parent for you: ```ruby d = Tag.find_or_create_by_path %w[a b c d] h = Tag.find_or_create_by_path %w[e f g h] e = h.root d.add_child(e) # "d.children << e" would work too, of course h.ancestry_path => ["a", "b", "c", "d", "e", "f", "g", "h"] ``` When it is more convenient to simply change the `parent_id` of a node directly (for example, when dealing with a form `