{"id":14955296,"url":"https://github.com/closuretree/closure_tree","last_synced_at":"2025-04-10T20:54:22.434Z","repository":{"id":1508341,"uuid":"1764724","full_name":"ClosureTree/closure_tree","owner":"ClosureTree","description":"Easily and efficiently make your ActiveRecord models support hierarchies","archived":false,"fork":false,"pushed_at":"2024-11-02T08:03:56.000Z","size":1301,"stargazers_count":1858,"open_issues_count":94,"forks_count":241,"subscribers_count":33,"default_branch":"master","last_synced_at":"2025-04-10T18:17:05.580Z","etag":null,"topics":["activerecord","ancestry","child","closure-tree","descendants","hierarchies","hierarchy","nested-hashes","nested-set","parent","rails","ruby","tree-structure"],"latest_commit_sha":null,"homepage":"https://closuretree.github.io/closure_tree/","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ClosureTree.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2011-05-18T06:28:17.000Z","updated_at":"2025-04-09T17:20:10.000Z","dependencies_parsed_at":"2025-01-09T01:30:40.403Z","dependency_job_id":"12e6b161-c9a0-40b2-92e3-1e66b3b3daad","html_url":"https://github.com/ClosureTree/closure_tree","commit_stats":{"total_commits":851,"total_committers":87,"mean_commits":9.781609195402298,"dds":0.2702702702702703,"last_synced_commit":"509f6dfa58da18bb4bff6ded0469263216579a90"},"previous_names":["mceachen/closure_tree"],"tags_count":86,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClosureTree%2Fclosure_tree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClosureTree%2Fclosure_tree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClosureTree%2Fclosure_tree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClosureTree%2Fclosure_tree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ClosureTree","download_url":"https://codeload.github.com/ClosureTree/closure_tree/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248297786,"owners_count":21080312,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["activerecord","ancestry","child","closure-tree","descendants","hierarchies","hierarchy","nested-hashes","nested-set","parent","rails","ruby","tree-structure"],"created_at":"2024-09-24T13:10:55.886Z","updated_at":"2025-04-10T20:54:22.409Z","avatar_url":"https://github.com/ClosureTree.png","language":"Ruby","readme":"# Closure Tree\n\n### Closure_tree lets your ActiveRecord models act as nodes in a [tree data structure](http://en.wikipedia.org/wiki/Tree_%28data_structure%29)\n\nCommon applications include modeling hierarchical data, like tags, threaded comments, page graphs in CMSes,\nand tracking user referrals.\n\n[![CI](https://github.com/ClosureTree/closure_tree/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/ClosureTree/closure_tree/actions/workflows/ci.yml)\n[![Gem Version](https://badge.fury.io/rb/closure_tree.svg)](https://badge.fury.io/rb/closure_tree)\n\nDramatically more performant than\n[ancestry](https://github.com/stefankroes/ancestry) and\n[acts_as_tree](https://github.com/amerine/acts_as_tree), and even more\nawesome than [awesome_nested_set](https://github.com/collectiveidea/awesome_nested_set/),\nclosure_tree has some great features:\n\n* __Best-in-class select performance__:\n  * Fetch your whole ancestor lineage in 1 SELECT.\n  * Grab all your descendants in 1 SELECT.\n  * Get all your siblings in 1 SELECT.\n  * Fetch all [descendants as a nested hash](#nested-hashes) in 1 SELECT.\n  * [Find a node by ancestry path](#find_or_create_by_path) in 1 SELECT.\n* __Best-in-class mutation performance__:\n  * 2 SQL INSERTs on node creation\n  * 3 SQL INSERT/UPDATEs on node reparenting\n* __Support for [concurrency](#concurrency)__ (using [with_advisory_lock](https://github.com/ClosureTree/with_advisory_lock))\n* __Tested against ActiveRecord 6.0+ with Ruby 2.7+__\n* Support for reparenting children (and all their descendants)\n* Support for [single-table inheritance (STI)](#sti) within the hierarchy\n* ```find_or_create_by_path``` for [building out heterogeneous hierarchies quickly and conveniently](#find_or_create_by_path)\n* Support for [deterministic ordering](#deterministic-ordering)\n* Support for [preordered](http://en.wikipedia.org/wiki/Tree_traversal#Pre-order) traversal of descendants\n* Support for rendering trees in [DOT format](http://en.wikipedia.org/wiki/DOT_(graph_description_language)), using [Graphviz](http://www.graphviz.org/)\n* Excellent [test coverage](#testing) in a comprehensive variety of environments\n\nSee [Bill Karwin](http://karwin.blogspot.com/)'s excellent\n[Models for hierarchical data presentation](http://www.slideshare.net/billkarwin/models-for-hierarchical-data)\nfor a description of different tree storage algorithms.\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Warning](#warning)\n- [Usage](#usage)\n- [Accessing Data](#accessing-data)\n- [Polymorphic hierarchies with STI](#polymorphic-hierarchies-with-sti)\n- [Deterministic ordering](#deterministic-ordering)\n- [Concurrency](#concurrency)\n- [FAQ](#faq)\n- [Testing](#testing)\n- [Change log](#change-log)\n\n## Installation\n\nNote that closure_tree only supports ActiveRecord 6.0 and later, and has test coverage for MySQL, PostgreSQL, and SQLite.\n\n1.  Add `gem 'closure_tree'` to your Gemfile\n\n2.  Run `bundle install`\n\n3.  Add `has_closure_tree` (or `acts_as_tree`, which is an alias of the same method) to your hierarchical model:\n\n    ```ruby\n    class Tag \u003c ActiveRecord::Base\n      has_closure_tree\n    end\n\n    class AnotherTag \u003c ActiveRecord::Base\n      acts_as_tree\n    end\n    ```\n\n    Make sure you check out the [large number of options](#available-options) that `has_closure_tree` accepts.\n\n    **IMPORTANT: Make sure you add `has_closure_tree` _after_ `attr_accessible` and\n    `self.table_name =` lines in your model.**\n\n    If you're already using other hierarchical gems, like `ancestry` or `acts_as_tree`, please refer\n    to the [warning section](#warning)!\n\n4.  Add a migration to add a `parent_id` column to the hierarchical model.\n    You may want to also [add a column for deterministic ordering of children](#deterministic-ordering), but that's optional.\n\n    ```ruby\n    class AddParentIdToTag \u003c ActiveRecord::Migration\n      def change\n        add_column :tags, :parent_id, :integer\n      end\n    end\n    ```\n\n    The column must be nullable. Root nodes have a `NULL` `parent_id`.\n\n5.  Run `rails g closure_tree:migration tag` (and replace `tag` with your model name)\n    to create the closure tree table for your model.\n\n    By default the table name will be the model's table name, followed by\n    \"_hierarchies\". Note that by calling ```has_closure_tree```, a \"virtual model\" (in this case, ```TagHierarchy```)\n    will be created dynamically. You don't need to create it.\n\n6.  Run `rake db:migrate`\n\n7.  If you're migrating from another system where your model already has a\n    `parent_id` column, run `Tag.rebuild!` and your\n    `tag_hierarchies` table will be truncated and rebuilt.\n\n    If you're starting from scratch you don't need to call `rebuild!`.\n\nNOTE: Run `rails g closure_tree:config` to create an initializer with extra\n      configurations. (Optional)\n\n## Warning\n\nAs stated above, using multiple hierarchy gems (like `ancestry` or `nested set`) on the same model\nwill most likely result in pain, suffering, hair loss, tooth decay, heel-related ailments, and gingivitis.\nAssume things will break.\n\n## Usage\n\n### Creation\n\nCreate a root node:\n\n```ruby\ngrandparent = Tag.create(name: 'Grandparent')\n```\n\nChild nodes are created by appending to the children collection:\n\n```ruby\nparent = grandparent.children.create(name: 'Parent')\n```\n\nOr by appending to the children collection:\n\n```ruby\nchild2 = Tag.new(name: 'Second Child')\nparent.children \u003c\u003c child2\n```\n\nOr by calling the \"add_child\" method:\n\n```ruby\nchild3 = Tag.new(name: 'Third Child')\nparent.add_child child3\n```\n\nOr by setting the parent on the child :\n\n```ruby\nTag.create(name: 'Fourth Child', parent: parent)\n```\n\nThen:\n\n```ruby\ngrandparent.self_and_descendants.collect(\u0026:name)\n=\u003e [\"Grandparent\", \"Parent\", \"First Child\", \"Second Child\", \"Third Child\", \"Fourth Child\"]\n\nchild1.ancestry_path\n=\u003e [\"Grandparent\", \"Parent\", \"First Child\"]\n```\n\n### find_or_create_by_path\n\nYou can `find` as well as `find_or_create` by \"ancestry paths\".\n\nIf you provide an array of strings to these methods, they reference the `name` column in your\nmodel, which can be overridden with the `:name_column` option provided to `has_closure_tree`.\n\n```ruby\nchild = Tag.find_or_create_by_path(%w[grandparent parent child])\n```\n\nAs of v5.0.0, `find_or_create_by_path` can also take an array of attribute hashes:\n\n```ruby\nchild = Tag.find_or_create_by_path([\n  {name: 'Grandparent', title: 'Sr.'},\n  {name: 'Parent', title: 'Mrs.'},\n  {name: 'Child', title: 'Jr.'}\n])\n```\n\nIf you're using STI, The attribute hashes can contain the `sti_name` and things work as expected:\n\n```ruby\nchild = Label.find_or_create_by_path([\n  {type: 'DateLabel', name: '2014'},\n  {type: 'DateLabel', name: 'August'},\n  {type: 'DateLabel', name: '5'},\n  {type: 'EventLabel', name: 'Visit the Getty Center'}\n])\n```\n\n### Moving nodes around the tree\n\nNodes can be moved around to other parents, and closure_tree moves the node's descendancy to the\nnew parent for you:\n\n```ruby\nd = Tag.find_or_create_by_path %w[a b c d]\nh = Tag.find_or_create_by_path %w[e f g h]\ne = h.root\nd.add_child(e) # \"d.children \u003c\u003c e\" would work too, of course\nh.ancestry_path\n=\u003e [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\"]\n```\n\nWhen it is more convenient to simply change the `parent_id` of a node directly (for example, when dealing with a form `\u003cselect\u003e`), closure_tree will handle the necessary changes automatically when the record is saved:\n\n```ruby\nj = Tag.find 102\nj.self_and_ancestor_ids\n=\u003e [102, 87, 77]\nj.update parent_id: 96\nj.self_and_ancestor_ids\n=\u003e [102, 96, 95, 78]\n```\n\n### Nested hashes\n\n```hash_tree``` provides a method for rendering a subtree as an\nordered nested hash:\n\n```ruby\nb = Tag.find_or_create_by_path %w(a b)\na = b.parent\nb2 = Tag.find_or_create_by_path %w(a b2)\nd1 = b.find_or_create_by_path %w(c1 d1)\nc1 = d1.parent\nd2 = b.find_or_create_by_path %w(c2 d2)\nc2 = d2.parent\n\nTag.hash_tree\n=\u003e {a =\u003e {b =\u003e {c1 =\u003e {d1 =\u003e {}}, c2 =\u003e {d2 =\u003e {}}}, b2 =\u003e {}}}\n\nTag.hash_tree(:limit_depth =\u003e 2)\n=\u003e {a =\u003e {b =\u003e {}, b2 =\u003e {}}}\n\nb.hash_tree\n=\u003e {b =\u003e {c1 =\u003e {d1 =\u003e {}}, c2 =\u003e {d2 =\u003e {}}}}\n\nb.hash_tree(:limit_depth =\u003e 2)\n=\u003e {b =\u003e {c1 =\u003e {}, c2 =\u003e {}}}\n```\n\n**If your tree is large (or might become so), use :limit_depth.**\n\nWithout this option, ```hash_tree``` will load the entire contents of that table into RAM. Your\nserver may not be happy trying to do this.\n\nHT: [ancestry](https://github.com/stefankroes/ancestry#arrangement) and [elhoyos](https://github.com/ClosureTree/closure_tree/issues/11)\n\n### Eager loading\n\nSince most of closure_tree's methods (e.g. `children`) return regular `ActiveRecord` scopes, you can use the `includes` method for eager loading, e.g.\n\n```ruby\ncomment.children.includes(:author)\n```\n\nHowever, note that the above approach only eager loads the requested associations for the immediate children of `comment`. If you want to walk through the entire tree, you may still end up making many queries and loading duplicate copies of objects.\n\nIn some cases, a viable alternative is the following:\n\n```ruby\ncomment.self_and_descendants.includes(:author)\n```\n\nThis would load authors for `comment` and all its descendants in a constant number of queries. However, the return value is an array of `Comment`s, and the tree structure is thus lost, which makes it difficult to walk the tree using elegant recursive algorithms.\n\nA third option is to use `has_closure_tree_root` on the model that is composed by the closure_tree model (e.g. a `Post` may be composed by a tree of `Comment`s). So in `post.rb`, you would do:\n\n```ruby\n# app/models/post.rb\nhas_closure_tree_root :root_comment\n```\n\nThis gives you a plain `has_one` association (`root_comment`) to the root `Comment` (i.e. that with null `parent_id`).\n\nIt also gives you a method called `root_comment_including_tree`, which you can invoke as follows:\n\n```ruby\na_post.root_comment_including_tree(:author)\n```\n\nThe result of this call will be the root `Comment` with all descendants _and_ associations loaded in a constant number of queries. Inverse associations are also setup on all nodes, so as you walk the tree, calling `children` or `parent` on any node will _not_ trigger any further queries and no duplicate copies of objects are loaded into memory.\n\nThe class and foreign key of `root_comment` are assumed to be `Comment` and `post_id`, respectively. These can be overridden in the usual way.\n\nThe same caveat stated above with `hash_tree` also applies here: this method will load the entire tree into memory. If the tree is very large, this may be a bad idea, in which case using the eager loading methods above may be preferred.\n\n### Graph visualization\n\n```to_dot_digraph``` is suitable for passing into [Graphviz](http://www.graphviz.org/).\n\nFor example, for the above tree, write out the DOT file with ruby:\n```ruby\nFile.open(\"example.dot\", \"w\") { |f| f.write(Tag.root.to_dot_digraph) }\n```\nThen, in a shell, ```dot -Tpng example.dot \u003e example.png```, which produces:\n\n![Example tree](https://raw.github.com/ClosureTree/closure_tree/master/img/example.png)\n\nIf you want to customize the label value, override the ```#to_digraph_label``` instance method in your model.\n\nJust for kicks, this is the test tree I used for proving that preordered tree traversal was correct:\n\n![Preordered test tree](https://raw.github.com/ClosureTree/closure_tree/master/img/preorder.png)\n\n### Available options\n\nWhen you include ```has_closure_tree``` in your model, you can provide a hash to override the following defaults:\n\n* ```:parent_column_name``` to override the column name of the parent foreign key in the model's table. This defaults to \"parent_id\".\n* ```:hierarchy_class_name``` to override the hierarchy class name. This defaults to the singular name of the model + \"Hierarchy\", like ```TagHierarchy```.\n* ```:hierarchy_table_name``` to override the hierarchy table name. This defaults to the singular name of the model + \"_hierarchies\", like ```tag_hierarchies```.\n* ```:dependent``` determines what happens when a node is destroyed. Defaults to ```nullify```.\n    * ```:nullify``` will simply set the parent column to null. Each child node will be considered a \"root\" node. This is the default.\n    * ```:delete_all``` will delete all descendant nodes (which circumvents the destroy hooks)\n    * ```:destroy``` will destroy all descendant nodes (which runs the destroy hooks on each child node)\n    * ```nil``` does nothing with descendant nodes\n* ```:name_column``` used by #```find_or_create_by_path```, #```find_by_path```, and ```ancestry_path``` instance methods. This is primarily useful if the model only has one required field (like a \"tag\").\n* ```:order``` used to set up [deterministic ordering](#deterministic-ordering)\n* ```:touch``` delegates to the `belongs_to` annotation for the parent, so `touch`ing cascades to all children (the performance of this for deep trees isn't currently optimal).\n\n## Accessing Data\n\n### Class methods\n\n* ```Tag.root``` returns an arbitrary root node\n* ```Tag.roots``` returns all root nodes\n* ```Tag.leaves``` returns all leaf nodes\n* ```Tag.hash_tree``` returns an [ordered, nested hash](#nested-hashes) that can be depth-limited.\n* ```Tag.find_by_path(path, attributes)``` returns the node whose name path is ```path```. See (#find_or_create_by_path).\n* ```Tag.find_or_create_by_path(path, attributes)``` returns the node whose name path is ```path```, and will create the node if it doesn't exist already.See (#find_or_create_by_path).\n* ```Tag.find_all_by_generation(generation_level)``` returns the descendant nodes who are ```generation_level``` away from a root. ```Tag.find_all_by_generation(0)``` is equivalent to ```Tag.roots```.\n* ```Tag.with_ancestor(ancestors)``` scopes to all descendants whose ancestors(s) is/are in the given list.\n* ```Tag.with_descendant(ancestors)``` scopes to all ancestors whose descendant(s) is/are in the given list.\n* ```Tag.lowest_common_ancestor(descendants)``` finds the lowest common ancestor of the descendants.\n### Instance methods\n\n* ```tag.root``` returns the root for this node\n* ```tag.root?``` returns true if this is a root node\n* ```tag.root_of?(node)``` returns true if current node is root of another one\n* ```tag.child?``` returns true if this is a child node. It has a parent.\n* ```tag.leaf?``` returns true if this is a leaf node. It has no children.\n* ```tag.leaves``` is scoped to all leaf nodes in self_and_descendants.\n* ```tag.depth``` returns the depth, or \"generation\", for this node in the tree. A root node will have a value of 0.\n* ```tag.parent``` returns the node's immediate parent. Root nodes will return nil.\n* ```tag.parent_of?(node)``` returns true if current node is parent of another one\n* ```tag.children``` is a ```has_many``` of immediate children (just those nodes whose parent is the current node).\n* ```tag.child_ids``` is an array of the IDs of the children.\n* ```tag.child_of?(node)``` returns true if current node is child of another one\n* ```tag.ancestors``` is a ordered scope of [ parent, grandparent, great grandparent, … ]. Note that the size of this array will always equal ```tag.depth```.\n* ```tag.ancestor_ids``` is an array of the IDs of the ancestors.\n* ```tag.ancestor_of?(node)``` returns true if current node is ancestor of another one\n* ```tag.self_and_ancestors``` returns a scope containing self, parent, grandparent, great grandparent, etc.\n* ```tag.self_and_ancestors_ids``` returns IDs containing self, parent, grandparent, great grandparent, etc.\n* ```tag.siblings``` returns a scope containing all nodes with the same parent as ```tag```, excluding self.\n* ```tag.sibling_ids``` returns an array of the IDs of the siblings.\n* ```tag.self_and_siblings``` returns a scope containing all nodes with the same parent as ```tag```, including self.\n* ```tag.descendants``` returns a scope of all children, childrens' children, etc., excluding self ordered by depth.\n* ```tag.descendant_ids``` returns an array of the IDs of the descendants.\n* ```tag.descendant_of?(node)``` returns true if current node is descendant of another one\n* ```tag.self_and_descendants``` returns a scope of self, all children, childrens' children, etc., ordered by depth.\n* ```tag.self_and_descendant_ids``` returns IDs of self, all children, childrens' children, etc., ordered by depth.\n* ```tag.family_of?``` returns true if current node and another one have a same root.\n* ```tag.hash_tree``` returns an [ordered, nested hash](#nested-hashes) that can be depth-limited.\n* ```tag.find_by_path(path)``` returns the node whose name path *from ```tag```* is ```path```. See (#find_or_create_by_path).\n* ```tag.find_or_create_by_path(path)``` returns the node whose name path *from ```tag```* is ```path```, and will create the node if it doesn't exist already.See (#find_or_create_by_path).\n* ```tag.find_all_by_generation(generation_level)``` returns the descendant nodes who are ```generation_level``` away from ```tag```.\n    * ```tag.find_all_by_generation(0).to_a``` == ```[tag]```\n    * ```tag.find_all_by_generation(1)``` == ```tag.children```\n    * ```tag.find_all_by_generation(2)``` will return the tag's grandchildren, and so on.\n* ```tag.destroy``` will destroy a node and do \u003cem\u003esomething\u003c/em\u003e to its children, which is determined by the ```:dependent``` option passed to ```has_closure_tree```.\n\n## Polymorphic hierarchies with STI\n\nPolymorphic models using single table inheritance (STI) are supported:\n\n1. Create a db migration that adds a String ```type``` column to your model\n2. Subclass the model class. You only need to add ```has_closure_tree``` to your base class:\n\n```ruby\nclass Tag \u003c ActiveRecord::Base\n  has_closure_tree\nend\nclass WhenTag \u003c Tag ; end\nclass WhereTag \u003c Tag ; end\nclass WhatTag \u003c Tag ; end\n```\n\nNote that if you call `rebuild!` on any of the subclasses, the complete Tag hierarchy will be emptied, thus taking the hiearchies of all other subclasses with it (issue #275). However, only the hierarchies for the class `rebuild!` was called on will be rebuilt, leaving the other subclasses without hierarchy entries.\n\nYou can work around that by overloading the `rebuild!` class method in all your STI subclasses and call the super classes `rebuild!` method:\n```ruby\nclass WhatTag \u003c Tag\n  def self.rebuild!\n    Tag.rebuild!\n  end\nend\n```\nThis way, the complete hierarchy including all subclasses will be rebuilt.\n\n## Deterministic ordering\n\nBy default, children will be ordered by your database engine, which may not be what you want.\n\nIf you want to order children alphabetically, and your model has a ```name``` column, you'd do this:\n\n```ruby\nclass Tag \u003c ActiveRecord::Base\n  has_closure_tree order: 'name'\nend\n```\n\nIf you want a specific order, add a new integer column to your model in a migration:\n\n```ruby\nt.integer :sort_order\n```\n\nand in your model:\n\n```ruby\nclass OrderedTag \u003c ActiveRecord::Base\n  has_closure_tree order: 'sort_order', numeric_order: true\nend\n```\n\nWhen you enable ```order```, you'll also have the following new methods injected into your model:\n\n* ```tag.siblings_before``` is a scope containing all nodes with the same parent as ```tag```,\n  whose sort order column is less than ```self```. These will be ordered properly, so the ```last```\n  element in scope will be the sibling immediately before ```self```\n* ```tag.siblings_after``` is a scope containing all nodes with the same parent as ```tag```,\n  whose sort order column is more than ```self```. These will be ordered properly, so the ```first```\n  element in scope will be the sibling immediately \"after\" ```self```\n\nIf your ```order``` column is an integer attribute, you'll also have these:\n\n* The class method ```#roots_and_descendants_preordered```, which returns all nodes in your tree,\n  [pre-ordered](http://en.wikipedia.org/wiki/Tree_traversal#Pre-order).\n\n* ```node1.self_and_descendants_preordered``` which will return descendants,\n  [pre-ordered](http://en.wikipedia.org/wiki/Tree_traversal#Pre-order).\n\n* ```node1.append_child(node2)``` (which is an alias to ```add_child```), which will\n  1. set ```node2```'s parent to ```node1```\n  2. set ```node2```'s sort order to place node2 last in the ```children``` array\n\n* ```node1.prepend_child(node2)``` which will\n  1. set ```node2```'s parent to ```node1```\n  2. set ```node2```'s sort order to place node2 first in the ```children``` array\n     Note that all of ```node1```'s children's sort_orders will be incremented\n\n* ```node1.prepend_sibling(node2)``` which will\n  1. set ```node2``` to the same parent as ```node1```,\n  2. set ```node2```'s order column to 1 less than ```node1```'s value, and\n  3. increment the order_column of all children of node1's parents whose order_column is \u003e node2's new value by 1.\n\n* ```node1.append_sibling(node2)``` which will\n  1. set ```node2``` to the same parent as ```node1```,\n  2. set ```node2```'s order column to 1 more than ```node1```'s value, and\n  3. increment the order_column of all children of node1's parents whose order_column is \u003e node2's new value by 1.\n\n```ruby\n\nroot = OrderedTag.create(name: 'root')\na = root.append_child(Label.new(name: 'a'))\nb = OrderedTag.create(name: 'b')\nc = OrderedTag.create(name: 'c')\n\n# We have to call 'root.reload.children' because root won't be in sync with the database otherwise:\n\na.append_sibling(b)\nroot.reload.children.pluck(:name)\n=\u003e [\"a\", \"b\"]\n\na.prepend_sibling(b)\nroot.reload.children.pluck(:name)\n=\u003e [\"b\", \"a\"]\n\na.append_sibling(c)\nroot.reload.children.pluck(:name)\n=\u003e [\"b\", \"a\", \"c\"]\n\nb.append_sibling(c)\nroot.reload.children.pluck(:name)\n=\u003e [\"b\", \"c\", \"a\"]\n```\n\n### Ordering Roots\n\nWith numeric ordering, root nodes are, by default, assigned order values globally across the whole database\ntable. So for instance if you have 5 nodes with no parent, they will be ordered 0 through 4 by default.\nIf your model represents many separate trees and you have a lot of records, this can cause performance\nproblems, and doesn't really make much sense.\n\nYou can disable this default behavior by passing `dont_order_roots: true` as an option to your delcaration:\n\n```\nhas_closure_tree order: 'sort_order', numeric_order: true, dont_order_roots: true\n```\n\nIn this case, calling `prepend_sibling` and `append_sibling` on a root node or calling\n`roots_and_descendants_preordered` on the model will raise a `RootOrderingDisabledError`.\n\nThe `dont_order_roots` option will be ignored unless `numeric_order` is set to true.\n\n\n## Concurrency\n\nSeveral methods, especially ```#rebuild``` and ```#find_or_create_by_path```, cannot run concurrently correctly.\n```#find_or_create_by_path```, for example, may create duplicate nodes.\n\nDatabase row-level locks work correctly with PostgreSQL, but MySQL's row-level locking is broken, and\nerroneously reports deadlocks where there are none. To work around this, and have a consistent implementation\nfor both MySQL and PostgreSQL, [with_advisory_lock](https://github.com/ClosureTree/with_advisory_lock)\nis used automatically to ensure correctness.\n\nIf you are already managing concurrency elsewhere in your application, and want to disable the use\nof with_advisory_lock, pass ```with_advisory_lock: false``` in the options hash:\n\n```ruby\nclass Tag\n  has_closure_tree with_advisory_lock: false\nend\n```\n\nNote that you *will eventually have data corruption* if you disable advisory locks, write to your\ndatabase with multiple threads, and don't provide an alternative mutex.\n\n## I18n\n\nYou can customize error messages using [I18n](http://guides.rubyonrails.org/i18n.html):\n\n```yaml\nen-US:\n  closure_tree:\n    loop_error: Your descendant cannot be your parent!\n```\n\n## FAQ\n\n### Are there any how-to articles on how to use this gem?\n\nYup! [Ilya Bodrov](https://github.com/bodrovis) wrote [Nested Comments with Rails](http://www.sitepoint.com/nested-comments-rails/).\n\n### Does this work well with ```#default_scope```?\n\n**No.** Please see [issue 86](https://github.com/ClosureTree/closure_tree/issues/86) for details.\n\n### Can I update parentage with `update_attribute`?\n\n**No.** `update_attribute` skips the validation hook that is required for maintaining the\nhierarchy table.\n\n### Can I assign a parent to multiple children with  ```#update_all```?\n\n**No.** Please see [issue 197](https://github.com/ClosureTree/closure_tree/issues/197) for details.\n\n### Does this gem support multiple parents?\n\nNo. This gem's API is based on the assumption that each node has either 0 or 1 parent.\n\nThe underlying closure tree structure will support multiple parents, but there would be many\nbreaking-API changes to support it. I'm open to suggestions and pull requests.\n\n### How do I use this with test fixtures?\n\nTest fixtures aren't going to be running your ```after_save``` hooks after inserting all your\nfixture data, so you need to call ```.rebuild!``` before your test runs. There's an example in\nthe spec ```tag_spec.rb```:\n\n```ruby\n  describe \"Tag with fixtures\" do\n    fixtures :tags\n    before :each do\n      Tag.rebuild! # \u003c- required if you use fixtures\n    end\n```\n\n**However, if you're just starting with Rails, may I humbly suggest you adopt a factory library**,\nrather than using fixtures? [Lots of people have written about this already](https://www.google.com/search?q=fixtures+versus+factories).\n\n### There are many ```lock-*``` files in my project directory after test runs\n\nThis is expected if you aren't using MySQL or Postgresql for your tests.\n\nSQLite doesn't have advisory locks, so we resort to file locking, which will only work\nif the ```FLOCK_DIR``` is set consistently for all ruby processes.\n\nIn your ```spec_helper.rb``` or ```minitest_helper.rb```, add a ```before``` and ```after``` block:\n\n```ruby\nbefore do\n  ENV['FLOCK_DIR'] = Dir.mktmpdir\nend\n\nafter do\n  FileUtils.remove_entry_secure ENV['FLOCK_DIR']\nend\n```\n\n### `bundle install` says `Gem::Ext::BuildError: ERROR: Failed to build gem native extension`\n\nWhen building from source, the `mysql2`, `pg`, and `sqlite` gems need their native client libraries\ninstalled on your system. Note that this error isn't specific to ClosureTree.\n\nOn Ubuntu/Debian systems, run:\n\n```\nsudo apt-get install libpq-dev libsqlite3-dev libmysqlclient-dev\nbundle install\n```\n\n### Object destroy fails with MySQL v5.7+\n\nA bug was introduced in MySQL's query optimizer. [See the workaround here](https://github.com/ClosureTree/closure_tree/issues/206).\n\n### Hierarchy maintenance errors from MySQL v5.7.9-v5.7.10\n\nUpgrade to MySQL 5.7.12 or later if you see [this issue](https://github.com/ClosureTree/closure_tree/issues/190):\n\n    Mysql2::Error: You can't specify target table '*_hierarchies' for update in FROM clause\n\n## Testing with Closure Tree\n\nClosure tree comes with some RSpec2/3 matchers which you may use for your tests:\n\n```ruby\nrequire 'spec_helper'\nrequire 'closure_tree/test/matcher'\n\ndescribe Category do\n # Should syntax\n it { should be_a_closure_tree }\n # Expect syntax\n it { is_expected.to be_a_closure_tree }\nend\n\ndescribe Label do\n # Should syntax\n it { should be_a_closure_tree.ordered }\n # Expect syntax\n it { is_expected.to be_a_closure_tree.ordered }\nend\n\ndescribe TodoList::Item do\n # Should syntax\n it { should be_a_closure_tree.ordered(:priority_order) }\n # Expect syntax\n it { is_expected.to be_a_closure_tree.ordered(:priority_order) }\nend\n\n```\n\n## Testing\n\nClosure tree is [tested under every valid combination](https://github.com/ClosureTree/closure_tree/blob/master/.github/workflows/ci.yml) of\n\n* Ruby 2.7+\n* ActiveRecord 6.0+\n* PostgreSQL, MySQL, and SQLite. Concurrency tests are only run with MySQL and PostgreSQL.\n\n```shell\n$ bundle\n$ appraisal bundle # this will install the matrix of dependencies\n$ appraisal rake # this will run the tests in all combinations\n$ appraisal activerecord-7.0 rake # this will run the tests in AR 7.0 only\n$ appraisal activerecord-7.0 rake spec # this will run rspec in AR 7.0 only\n$ appraisal activerecord-7.0 rake test # this will run minitest in AR 7.0 only\n```\n\nBy default the test are run with sqlite3 only. \nYou run test with other databases by passing the database url as environment variable:\n\n```shell\n$ DATABASE_URL=postgres://localhost/my_database appraisal activerecord-7.0 rake test \n```\n\n## Change log\n\nSee the [change log](https://github.com/ClosureTree/closure_tree/blob/master/CHANGELOG.md).\n\n## Thanks to\n\n* The 45+ engineers around the world that have contributed their time and code to this gem\n  (see the [changelog](https://github.com/ClosureTree/closure_tree/blob/master/CHANGELOG.md)!)\n* https://github.com/collectiveidea/awesome_nested_set\n* https://github.com/patshaughnessy/class_factory\n* JetBrains, which provides an [open-source license](http://www.jetbrains.com/ruby/buy/buy.jsp#openSource) to\n  [RubyMine](http://www.jetbrains.com/ruby/features/) for the development of this project.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclosuretree%2Fclosure_tree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclosuretree%2Fclosure_tree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclosuretree%2Fclosure_tree/lists"}