README.md

# 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).