{"id":14775077,"url":"https://github.com/1and1/acts_as_recursive_tree","last_synced_at":"2025-04-13T00:49:22.597Z","repository":{"id":17590872,"uuid":"82297773","full_name":"1and1/acts_as_recursive_tree","owner":"1and1","description":"Make use of recursive queries in Rails when using Postgresql or SQLite ","archived":false,"fork":false,"pushed_at":"2024-11-12T09:08:37.000Z","size":156,"stargazers_count":80,"open_issues_count":7,"forks_count":3,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-13T00:49:17.663Z","etag":null,"topics":["activerecord","postgresql","rails","ruby","sqlite3"],"latest_commit_sha":null,"homepage":null,"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/1and1.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","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":"2017-02-17T13:00:44.000Z","updated_at":"2025-04-08T10:48:02.000Z","dependencies_parsed_at":"2025-01-10T05:46:41.127Z","dependency_job_id":null,"html_url":"https://github.com/1and1/acts_as_recursive_tree","commit_stats":{"total_commits":96,"total_committers":3,"mean_commits":32.0,"dds":0.375,"last_synced_commit":"38896c45372551e5a115c4e25099640466b265f4"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1and1%2Facts_as_recursive_tree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1and1%2Facts_as_recursive_tree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1and1%2Facts_as_recursive_tree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1and1%2Facts_as_recursive_tree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/1and1","download_url":"https://codeload.github.com/1and1/acts_as_recursive_tree/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248650436,"owners_count":21139672,"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","postgresql","rails","ruby","sqlite3"],"created_at":"2024-09-16T23:01:39.521Z","updated_at":"2025-04-13T00:49:22.578Z","avatar_url":"https://github.com/1and1.png","language":"Ruby","funding_links":[],"categories":["ORM/ODM Extensions","Ruby"],"sub_categories":[],"readme":"# ActsAsRecursiveTree\n\n[![CI Status](https://github.com/1and1/acts_as_recursive_tree/workflows/CI/badge.svg?branch=main)](https://github.com/1and1/acts_as_recursive_tree/actions?query=workflow%3ACI+branch%3Amaster)\n[![Gem Version](https://badge.fury.io/rb/acts_as_recursive_tree.svg)](https://badge.fury.io/rb/acts_as_recursive_tree)\n\nUse the power of recursive SQL statements in your Rails application.\n\nWhen you have tree based data in your application, you always to struggle with retrieving data. There are solutions, but the always come at a price:\n\n  * Nested Set is fast at retrieval, but when inserting you might have to rearrange bounds, which can be very complex\n  * Closure_Tree stores additional data in a separate table, which has be kept up to date\n\nLuckily, there is already a SQL standard that makes it very easy to retrieve data in the traditional parent/child relation. Currently this is only supported in sqlite and Postgres. With this it is possible to query complete trees without the need of extra tables or indices.\n\n## Supported environments\nActsAsRecursiveTree currently supports following ActiveRecord versions and is tested for compatibility:\n  * ActiveRecord 7.0.x\n  * ActiveRecord 7.1.x\n  * ActiveRecord 7.2.x\n  * ActiveRecord NEXT (from git)\n\n## Supported Rubies\nActsAsRecursiveTree is tested with following rubies:\n  * MRuby 3.1\n  * MRuby 3.2\n  * MRuby 3.3\n\nOther Ruby implementations are not tested, but should also work.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'acts_as_recursive_tree'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install acts_as_recursive_tree\n\n\nIn your model class add following line:\n\n```ruby\nclass Node  \u003c ActiveRecord::Base\n  recursive_tree\nend\n```\nThat's it. This will assume that your model has a column named `parent_id` which will be used for traversal. If your column is something different, then you can specify it in the call to `recursive_tree`:\n\n```ruby\nrecursive_tree parent_key: :some_other_column\n```\n\nSome extra special stuff - if your parent relation is also polymorphic, then specify the polymorphic column:\n\n```ruby\nrecursive_tree parent_type_column: :some_other_type_column\n```\n\nControlling deletion behaviour:\n\nBy default, it is up to the user code to delete all child nodes in a tree when a parent node gets deleted. This can be controlled by the `:dependent` option, which will be set on the `children` association (see [#has_many](https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many) in the Rails doc).\n\n```ruby\nrecursive_tree dependent: :nullify # or :destroy, etc.\n```\n\n## Usage\n\nAfter you set up a model for usage, there are now several methods you can use.\n\n### Associations\n\nYou have access to following associations:\n\n   * `parent` - the parent of this instance\n   * `children` - all children (parent_id = self.id)\n   * `self_and_siblings` - all node where parent_id = self.parent_id\n\n### Class Methods\n\n  * `roots` - all root elements (parent_id = nil)\n  * `self_and_descendants_of(reference)` - the complete tree of `reference` __including__ `reference` in the result\n  * `descendants_of(reference)` - the complete tree of `reference` __excluding__ `reference` in the result\n  * `leaves_of(reference)` - special case of descendants where only those elements are returned, that do not have any children\n  * `self_and_ancestors_of(reference)` - the complete ancestor list of `reference` __including__ `reference` in the result\n  * `ancestors_of(reference)` - the complete ancestor list of `reference` __excluding__ `reference` in the result\n  * `roots_of(reference)` - special case of ancestors where only those elements are returned, that do not have any parent \n\nYou can pass in following argument types for `reference`, that will be accepted:\n  * `integer` - simple integer value\n\n```ruby\n    Node.descendants_of(1234)\n```\n\n  * `array` - array of integer value\n\n```ruby\n    Node.descendants_of([1234, 5678])\n```\n\n  * `ActiveRecord::Base` - instance of an AR::Model class\n\n```ruby\n    Node.descendants_of(some_node)\n```\n\n  * `ActiveRecord::Relation` - an AR::Relation form the same type\n\n```ruby\n    Node.descendants_of(Node.where(foo: :bar))\n```\n\n\n### Instance Methods\n\nFor nearly all mentioned scopes and associations there is a corresponding instance method:\n\n  * `root` - returns the root element of this node\n  * `self_and_descendants` - the complete tree __including__ `self` in the result\n  * `descendants` - the complete tree __excluding__ `self` in the result\n  * `leaves` - only leaves of this node\n  * `self_and_ancestors` - the complete ancestor list __including__ `self` in the result\n  * `ancestors` - the complete ancestor list __excluding__ `self` in the result\n  \nThose methods simply delegate to the corresponding scope and pass `self` as reference.\n\n__Additional methods:__\n  * `siblings` - return all elements where parent_id = self.parent_id __excluding__ `self`\n  * `self_and_children` - return all children and self as a Relation\n  \n__Utility methods:__\n  * `root?` - returns true if this node is a root node\n  * `leaf?` - returns true if this node is a leave node\n  * `preload_tree` - fetches all descendants of this node and assigns the proper parent/children associations. You are then able to traverse the tree through the children/parent association without querying the database again. You can also pass arguments to `includes` which will be forwarded when fetching records.  \n\n```ruby\n    node.preload_tree(includes: [:association, :another_association])\n```\n\n## Customizing the recursion\n\nAll *ancestors* and *descendants* methods/scopes can take an additional block argument. The block receives ans `opts` argument with which you are able to customize the recursion.\n\n\n__Depth__\n\nSpecify a depth condition. Only the elements matching the depth are returned.\nSupported operations are:\n  * `==` exact match - can be Integer or Range or Array. When specifying a Range this will result in a `depth BETWEEN min AND max` query. \n  * `!=` except - can be Integer or Array\n  * `\u003e` greater than - only Integer\n  * `\u003e=` greater than or equals - only Integer\n  * `\u003c` less than - only Integer\n  * `\u003c=` less than or equals - only Integer\n\n```ruby\nNode.descendants_of(1){|opts| opts.depth == 3..6 }\nnode_instance.descendants{ |opts| opts.depth \u003c= 4 }\nnode_instance.descendants{ |opts| opts.depth != [4, 7] }\n```\nNOTE: `depth == 1` is the same as `children/parent`\n \n__Condition__\n\nPass in an additional relation. Only those elements are returned where the condition query matches. \n\n```ruby\nNode.descendants_of(1){|opts| opts.condition = Node.where(active: true) }\nnode_instance.descendants{ |opts| opts.condition = Node.where(active: true) }\n```\nNOTE: In contrast to depth, which first gathers the complete tree and then discards all non matching results, this will stop the recursive traversal when the relation is not met. Following two lines are completely different when executed: \n\n```ruby\nnode_instance.descendants.where(active: true) # =\u003e returns the complete tree and filters than out only the active ones\nnode_instance.descendants{ |opts| opts.condition = Node.where(active: true) } # =\u003e stops the recursion when encountering a non active node, which may return less results than the one above\n```\n\n__Ordering__\nAll the *ancestor* methods will order the result depending on the depth of the recursion. Ordering for the *descendants* methods is disabled by default, but can be enabled if needed.\n\n```ruby\nNode.descendants_of(1){|opts| opts.ensure_ordering! }\nnode_instance.descendants{ |opts| opts.ensure_ordering! }\n```\n\nNOTE: if there are many descendants this may cause a severe increase in execution time! \n\n## Single Table Inheritance (STI)\n\nSTI works out of the box. Consider following classes: \n\n```ruby\nclass Node  \u003c ActiveRecord::Base\n  recursive_tree\nend\n\nclass SubNode  \u003c Node\n  \nend\n```\n\nWhen calling ClassMethods the results depend on the class on which you call the method:\n\n```ruby\nNode.descendants_of(123) # =\u003e returns Node and SubNode instances\nSubNode.descendants_of(123) # =\u003e returns SubNode instances only\n```\n\nInstance Methods make no difference of the class from which they are called:\n\n```ruby\nsub_node_instance.descendants # =\u003e returns Node and SubNode instances\n```\n\n## A note on endless recursion / cycle detection\n\n### Inserting\nAs of now it is up to the user code to guarantee there will be no cycles created in the parent/child entries. If not, your DB might run into an endless recursion. Inserting/updating records that will cause a cycle is not prevented by some validation checks, so you have to do this by your own. This might change in a future version.\n\n### Querying\nIf you want to make sure to not run into an endless recursion when querying, then there are following options:\n1. Add a maximum depth to the query options. If an cycle is present in your data, the recursion will stop when reaching the max depth and stop further traversing.\n2. When you are on recent version of PostgreSQL (14+) you are lucky. Postgres added the CYCLE detection feature to detect cycles and prevent endless recursion. Our query builder will add this feature if your DB does support this.  \n\n## Contributing\n\n1. Fork it ( https://github.com/1and1/acts_as_recursive_tree/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F1and1%2Facts_as_recursive_tree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F1and1%2Facts_as_recursive_tree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F1and1%2Facts_as_recursive_tree/lists"}