Skip to content

amplifiedai/amplified_nested

Amplified.Nested

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.

Installation

Add amplified_nested to your list of dependencies in mix.exs:

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:

# 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:

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:

use Amplified.Nested, parent: :parent_tag_id, child: :sub_tags, id: :tag_id

Usage

Nesting a flat list into a tree

tree =
  Tag
  |> Repo.all()
  |> Tag.nest()

Works with {:ok, list} tuples from Repo operations:

Tag.nest({:ok, tags})

Flattening a tree back into a list

Tag.flatten(tree)
# => [%Tag{id: 1, ...}, %Tag{id: 2, ...}, ...]

Finding ancestors

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"}]

Finding descendants

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"}]

Filtering with ancestor preservation

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"}]

Documentation

Full documentation is available on HexDocs.

Licence

MIT — see LICENCE.md.

About

Utilities for working with hierarchical (tree-structured) data.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages