# Amplified.Nested
Utilities for working with hierarchical (tree-structured) data in Elixir.
```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.
## Installation
Add `amplified_nested` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:amplified_nested, "~> 0.1.0"}
]
end
```
## Configuration
Configure the Ecto repo if you use `descendant_ancestry/3` on structs whose
`:children` association has not been loaded:
```elixir
# config/config.exs
config :amplified_nested, repo: MyApp.Repo
```
If you never use `descendant_ancestry/3` with unloaded associations, no
configuration is needed.
## Setup
Schema modules opt in with `use Amplified.Nested`:
```elixir
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
end
```
This generates delegating functions on the schema module so you can call
`Tag.nest(tags)` or `Tag.flatten(tag)` directly, with field names baked in.
### Custom field names
If your schema uses non-standard field names, pass them as options:
```elixir
use Amplified.Nested, parent: :parent_tag_id, child: :sub_tags, id: :tag_id
```
## Usage
### Nesting a flat list into a tree
```elixir
tree =
Tag
|> Repo.all()
|> Tag.nest()
```
Works with `{:ok, list}` tuples from Repo operations:
```elixir
Tag.nest({:ok, tags})
```
### Flattening a tree back into a list
```elixir
Tag.flatten(tree)
# => [%Tag{id: 1, ...}, %Tag{id: 2, ...}, ...]
```
### Finding ancestors
Returns the full ancestry chain from root to entity:
```elixir
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"}]
```
### Finding descendants
Returns all descendants plus the entity itself:
```elixir
Tag.descendants(all_tags, elixir_tag)
# => [%Tag{name: "Elixir"}, %Tag{name: "Phoenix"}, %Tag{name: "LiveView"}, %Tag{name: "Ecto"}]
```
### Filtering with ancestor preservation
Filter a nested tree, keeping matching nodes and their ancestors:
```elixir
Tag.filter(tree, &(&1.name == "LiveView"))
# => [%Tag{name: "Elixir"}, %Tag{name: "Phoenix"}, %Tag{name: "LiveView"}]
```
## Documentation
Full documentation is available on [HexDocs](https://hexdocs.pm/amplified_nested).
## Licence
MIT — see [LICENCE.md](LICENCE.md).