{"id":15784639,"url":"https://github.com/secretworry/as_nested_set","last_synced_at":"2026-04-01T19:18:56.697Z","repository":{"id":46048540,"uuid":"60522995","full_name":"secretworry/as_nested_set","owner":"secretworry","description":"a ecto based nested set implementation for database","archived":false,"fork":false,"pushed_at":"2021-11-17T21:18:59.000Z","size":173,"stargazers_count":35,"open_issues_count":2,"forks_count":9,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-03-28T00:57:57.050Z","etag":null,"topics":["database","ecto","elixir","nested-set","tree"],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/secretworry.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-06-06T11:33:36.000Z","updated_at":"2025-04-14T15:02:32.000Z","dependencies_parsed_at":"2022-09-26T18:30:51.513Z","dependency_job_id":null,"html_url":"https://github.com/secretworry/as_nested_set","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/secretworry/as_nested_set","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/secretworry%2Fas_nested_set","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/secretworry%2Fas_nested_set/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/secretworry%2Fas_nested_set/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/secretworry%2Fas_nested_set/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/secretworry","download_url":"https://codeload.github.com/secretworry/as_nested_set/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/secretworry%2Fas_nested_set/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31291118,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["database","ecto","elixir","nested-set","tree"],"created_at":"2024-10-04T20:04:41.180Z","updated_at":"2026-04-01T19:18:56.675Z","avatar_url":"https://github.com/secretworry.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# as_nested_set\n\n[![Build Status](https://travis-ci.com/secretworry/as_nested_set.svg?branch=master)](https://travis-ci.org/secretworry/as_nested_set)\n[![Coveralls Coverage](https://img.shields.io/coveralls/secretworry/as_nested_set.svg)](https://coveralls.io/github/secretworry/as_nested_set)\n[![Hex.pm](https://img.shields.io/hexpm/v/as_nested_set.svg)](http://hex.pm/packages/as_nested_set)\n\n**An [ecto](https://github.com/elixir-lang/ecto) based [Nested set model](https://en.wikipedia.org/wiki/Nested_set_model) implementation for database**\n\n## Installation\n\nAdd as_nested_set to your list of dependencies in `mix.exs`:\n\n```elixir\n  # use the stable version\n  def deps do\n    [{:as_nested_set, \"~\u003e 3.4\"}]\n  end\n\n  # use the latest version\n  def deps do\n    [{:as_nested_set, github: \"https://github.com/secretworry/as_nested_set.git\"}]\n  end\n```\n\n## Usage\n\nTo make use of `as_nested_set`, 4 fields( `id`, `lft`, `rgt` and `parent_id`) are required for your model. The name of those fields are configurable.\n\n```elixir\ndefmodule AsNestedSet.TestRepo.Migrations.MigrateAll do\n  use Ecto.Migration\n\n  def change do\n    create table(:taxons) do\n      add :name, :string\n      add :taxonomy_id, :id\n      add :parent_id, :id\n      add :lft, :integer\n      add :rgt, :integer\n\n      timestamps\n    end\n\n  end\nend\n```\n\nEnable the nested set functionality by `use AsNestedSet` on your model\n\n```elixir\ndefmodule AsNestedSet.Taxon do\n  use AsNestedSet, scope: [:taxonomy_id]\n  # ...\nend\n```\n\n## Options\n\nYou can config the name of required fields through attributes:\n\n```elixir\ndefmodule AsNestedSet.Taxon do\n  @right_column :right\n  @left_column :left\n  @node_id_column :node_id\n  @parent_id_column :pid\n  # ...\nend\n```\n\n  * `@right_column`: column name for the right boundary (defaults to `lft`, since left is a reserved keyword for Mysql)\n  * `@left_column`: column name for the left boundary (defaults to `rgt`, reserved too)\n  * `@node_id_column`:  specifies the name for the node id column (defaults to `id`, i.e. the id of the model, change this if you want to have a different id, or you id field is different)\n  * `@parent_id_column`: specifies the name for the parent id column (defaults to `parent_id`)\n\nYou can also pass following arguments to modify its behavior:\n\n```elixir\ndefmodule AsNestedSet.Taxon do\n  use AsNestedSet, scope: [:taxonomy_id]\n  # ...\nend\n```\n\n  * `scope`: (optional) a list of column names which restrict what are to be considered within the same tree(same scope). When ignored, all the nodes will be considered under the same tree.\n\n## Model Operations\n\nOnce you have set up you model, you can then\n\nAdd new nodes\n\n```elixir\ntarget = Repo.find!(Taxon, 1)\n# add to left\n%Taxon{name: \"left\", taxonomy_id: 1} |\u003e AsNestedSet.create(target, :left) |\u003e AsNestedSet.execute(TestRepo)\n# add to right\n%Taxon{name: \"right\", taxonomy_id: 1} |\u003e AsNestedSet.create(target, :right) |\u003e AsNestedSet.execute(TestRepo)\n# add as first child\n%Taxon{name: \"child\", taxonomy_id: 1} |\u003e AsNestedSet.create(target, :child) |\u003e AsNestedSet.execute(TestRepo)\n# add as parent\n%Taxon{name: \"parent\", taxonomy_id: 1} |\u003e AsNestedSet.create(target, :parent) |\u003e AsNestedSet.execute(TestRepo)\n\n# add as root\n%Taxon{name: \"root\", taxonomy_id: 1} |\u003e AsNestedSet.create(:root) |\u003e AsNestedSet.execute(TestRepo)\n\n# move a node to a new position\n\nnode |\u003e AsNestedSet.move(:root) |\u003e AsNestedSet.execute(TestRepo) // move the node to be a new root\nnode |\u003e AsNestedSet.move(target, :left) |\u003e AsNestedSet.execute(TestRepo) // move the node to the left of the target\nnode |\u003e AsNestedSet.move(target, :right) |\u003e AsNestedSet.execute(TestRepo) // move the node to the right of the target\nnode |\u003e AsNestedSet.move(target, :child) |\u003e AsNestedSet.execute(TestRepo) // move the node to be the right-most child of target\n\n```\n\nRemove a specified node and all its descendants\n\n```elixir\ntarget = Repo.find!(Taxon, 1)\nAsNestedSet.delete(target) |\u003e AsNestedSet.execute(TestRepo)\n```\n\nQuery different nodes\n\n```elixir\n\n# find all roots\nAsNestedSet.roots(Taxon, %{taxonomy_id: 1}) |\u003e AsNestedSet.execute(TestRepo)\n\n# find all children of target\nAsNestedSet.children(target) |\u003e AsNestedSet.execute(TestRepo)\n\n# find all the leaves for given scope\nAsNestedSet.leaves(Taxon, %{taxonomy_id: 1}) |\u003e AsNestedSet.execute(TestRepo)\n\n# find all descendants\nAsNestedSet.descendants(target) |\u003e AsNestedSet.execute(TestRepo)\n# include self\nAsNestedSet.self_and_descendants(target) |\u003e AsNestedSet.execute(TestRepo)\n\n# find all ancestors\nAsNestedSet.ancestors(target) |\u003e AsNestedSet.execute(TestRepo)\n\n#find all siblings (self included)\nAsNestedSet.self_and_siblings(target) |\u003e AsNestedSet.execute(TestRepo)\n\n```\n\nTraverse the tree\n```elixir\n# traverse a tree with 3 args post callback\nAsNestedSet.traverse(Taxon, %{taxonomy_id}, context, fn node, context -\u003e {node, context}, end, fn node, children, context -\u003e {node, context} end) |\u003e AsNestedSet.execute(TestRepo)\n# traverse a tree with 2 args post callback\nAsNestedSet.traverse(Taxon, %{taxonomy_id}, context, fn node, context -\u003e {node, context}, end, fn node, context -\u003e {node, context} end) |\u003e AsNestedSet.execute(TestRepo)\n\n# traverse a subtree with 3 args post callback\nAsNestedSet.traverse(target, context, fn node, context -\u003e {node, context}, end, fn node, children, context -\u003e {node, context} end) |\u003e AsNestedSet.execute(TestRepo)\n# traverse a tree with 2 args post callback\nAsNestedSet.traverse(target, context, fn node, context -\u003e {node, context}, end, fn node, context -\u003e {node, context} end) |\u003e AsNestedSet.execute(TestRepo)\n```\n\n# FAQ\n\n## How to ensure the consistency\n\n*We recommend users to use a transaction to wrap all the operations in the production environment*\n\nWe introduced the `@type executable`( a delayed execution ) as the return value of each API, so using transaction or not and how granular the transaction should be are all up to users.\n\nIn general, almost all modifications of a nested set can be done in one SQL, but we can't express some of them using ecto's DSL( ecto doesn't support `case-when` in update query ), so users having concurrent modifications *must* wrap `AsNestedSet.execute(call, repo)` in a Transaction, for example\n\n```elixir\nexec = node |\u003e AsNestedSet.move(:root)\nRepo.transaction fn -\u003e AsNestedSet.execute(exec, Repo) end\n```\n\nThe `node` passed in as an argument might has changed after loaded from db, we will reload it from DB before use it, so there is no need to wrap the `load` and the `execute` in the same transaction\n\n```elixir\n# This is not necessary\nRepo.transaction fn -\u003e\n\tnode = loadNode()\n\tnode |\u003e AsNestedSet.move(:root) |\u003e AsNestedSet.execute(Repo) # We will reload the node passed in\nend\n```\n\nBut if you want to ensure consistency across multiple `execute`s , to avoid the racing condition, you have to isolate them by wrap them in different transactions.\n\n## How to move a node to be the n-th child of a target\n\nBe default, after using `AsNestedSet.move(node, target, :child)`, you move the `node` to be the right-most child of the `target`, because we can know the `left` and `right` of the target right way, but to find out the proper `right` and `left` for n-th child requires more operations.\n\nTo achieve the goal, you should:\n  1. Query the n-th child or (n-1)th child of the target by `AsNestedSet.children(target)`,\n  2. Use `move(node, n_th_child, :left)` and `move(node, n_1_th_child, :right)` respectively.\n\n## Ecto 2.x\n\n`Ecto` is upgrading to 3.0, with a clear API and a lot lot of bug fixes. Please consider to upgrade for your projects too:)\nWe will not support 2.x in our public releases, but if you are using Ecto 2.x, you can get the latest updates by using branch `ecto-2.x`\n\n# Contributors\n\n* [@SagarKarwande](https://github.com/SagarKarwande)\n* [@oyeb](https://github.com/oyeb)\n* [@nicholasjhenry](https://github.com/nicholasjhenry)\n* [@montebrown](https://github.com/montebrown)\n\n# Special thanks\n\n* Thanks [Travis CI](https://travis-ci.com/) for providing free and convenient integration test\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsecretworry%2Fas_nested_set","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsecretworry%2Fas_nested_set","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsecretworry%2Fas_nested_set/lists"}