Utilities for working with hierarchical (tree-structured) data in Elixir.
tags = Repo.all(Tag)
tree = Tag.nest(tags)Many applications have self-referential schemas — categories with
subcategories, comments with replies, tag taxonomies. These are typically
stored as flat rows with a :parent_id foreign key and a :children
association. Amplified.Nested provides a small, composable toolkit for
the operations you almost always need: nesting a flat list into a tree,
flattening a tree back into a list, walking ancestors and descendants, and
filtering while preserving the ancestor chain.
All functions are accessed through the schema module (e.g. Tag.nest/1,
Tag.ancestors/2) — not by calling Amplified.Nested directly.
Add amplified_nested to your list of dependencies in mix.exs:
def deps do
[
{:amplified_nested, "~> 0.1.0"}
]
endConfigure the Ecto repo if you use descendant_ancestry/3 on structs whose
:children association has not been loaded:
# config/config.exs
config :amplified_nested, repo: MyApp.RepoIf you never use descendant_ancestry/3 with unloaded associations, no
configuration is needed.
Schema modules opt in with use Amplified.Nested:
defmodule MyApp.Tags.Tag do
use Ecto.Schema
use Amplified.Nested
schema "tags" do
field :name, :string
belongs_to :parent, __MODULE__
has_many :children, __MODULE__, foreign_key: :parent_id
end
endThis generates delegating functions on the schema module so you can call
Tag.nest(tags) or Tag.flatten(tag) directly, with field names baked in.
If your schema uses non-standard field names, pass them as options:
use Amplified.Nested, parent: :parent_tag_id, child: :sub_tags, id: :tag_idtree =
Tag
|> Repo.all()
|> Tag.nest()Works with {:ok, list} tuples from Repo operations:
Tag.nest({:ok, tags})Tag.flatten(tree)
# => [%Tag{id: 1, ...}, %Tag{id: 2, ...}, ...]Returns the full ancestry chain from root to entity:
all_tags = Repo.all(Tag)
liveview_tag = Enum.find(all_tags, &(&1.name == "LiveView"))
Tag.ancestors(all_tags, liveview_tag)
# => [%Tag{name: "Elixir"}, %Tag{name: "Phoenix"}, %Tag{name: "LiveView"}]Returns all descendants plus the entity itself:
Tag.descendants(all_tags, elixir_tag)
# => [%Tag{name: "Elixir"}, %Tag{name: "Phoenix"}, %Tag{name: "LiveView"}, %Tag{name: "Ecto"}]Filter a nested tree, keeping matching nodes and their ancestors:
Tag.filter(tree, &(&1.name == "LiveView"))
# => [%Tag{name: "Elixir"}, %Tag{name: "Phoenix"}, %Tag{name: "LiveView"}]Full documentation is available on HexDocs.
MIT — see LICENCE.md.