# `AncestryEx`
[🔗](https://github.com/rocket4ce/ancestry_ex/blob/v0.1.0/lib/ancestry_ex.ex#L1)

Organise the records of an Ecto schema as a tree (or a forest of trees)
using a materialized path column, like Ruby's
[ancestry](https://github.com/stefankroes/ancestry) gem.

Every node stores the ids of all its ancestors in a single string column
(`"/1/5/12/"`), so every navigation (parent, ancestors, children,
descendants, siblings, depth...) is a single indexed query, and moving a
subtree is a single `UPDATE`.

## Setup

    defmodule MyApp.Catalog.Category do
      use Ecto.Schema
      use AncestryEx, orphan_strategy: :rootify, cache_depth: true

      import Ecto.Changeset

      schema "categories" do
        field :name, :string
        ancestry_fields()
        timestamps()
      end

      def changeset(category, attrs) do
        category
        |> cast(attrs, [:name])
        |> validate_required([:name])
        |> AncestryEx.Changeset.cast_parent()
      end
    end

`mix ancestry_ex.gen.migration` adds the columns to an existing table, and
`mix ancestry_ex.gen.schema` / `mix ancestry_ex.gen.context` generate a new
schema (and context) from scratch.

## Options

  * `:ancestry_column` - name of the column, defaults to `:ancestry`.
  * `:ancestry_format` - `:materialized_path2` (default) or
    `:materialized_path`. See `AncestryEx.Format`.
  * `:orphan_strategy` - what happens to the descendants of a deleted node
    (see `AncestryEx.Changeset.handle_orphans/2`):
    * `:destroy` (default) - delete them.
    * `:rootify` - the children become roots.
    * `:adopt` - the children are attached to the parent of the deleted node.
    * `:restrict` - refuse to delete a node with children.
    * `:none` - do nothing.
  * `:cache_depth` - `true` to keep the depth of every node in an integer
    column, `:virtual` to use a PostgreSQL generated column instead.
    Defaults to `false`; depth scopes still work, computing it in SQL.
  * `:depth_cache_column` - defaults to `:ancestry_depth`.
  * `:counter_cache` - `true` (column `:children_count`) or a column name
    to keep the number of children of every node.
  * `:touch` - when `true`, ancestors get their autoupdated timestamps
    (`updated_at`) bumped whenever a descendant is saved or deleted.

## Generated functions

`use AncestryEx` defines these functions in the schema. Query functions
take an optional queryable as first argument and return an `Ecto.Query`.

| Query                       | Pure (no database)                 |
| --------------------------- | ---------------------------------- |
| `roots/0,1`                 | `root?/1`, `has_parent?/1`         |
| `parent/1,2`                | `parent_id/1`, `parent_of?/2`      |
| `root/1,2`                  | `root_id/1`, `root_of?/2`          |
| `ancestors/1,2`             | `ancestor_ids/1`, `ancestor_of?/2` |
| `path/1,2`                  | `path_ids/1`                       |
| `children/1,2`              | `child_of?/2`, `child_ancestry/1`  |
| `siblings/1,2`              | `sibling_of?/2`                    |
| `descendants/1,2`           | `descendant_of?/2`                 |
| `subtree/1,2`               | `in_subtree_of?/2`                 |
| `indirects/1,2`             | `indirect_of?/2`                   |
| `leaves/0,1`                | `depth/1`                          |
| `before_depth/1,2`, `to_depth/1,2`, `at_depth/1,2`, `from_depth/1,2`, `after_depth/1,2` | |
| `ordered_by_ancestry/0,1`   |                                    |

All of them can be overridden. The pure functions are also available
directly in this module (`AncestryEx.parent_id(node)`), together with the
tree helpers `arrange/1`, `arrange_serializable/2` and `sort_by_ancestry/1`.

# `ancestor_ids`

Ids of the ancestors of `node`, root first.

# `ancestor_of?`

Returns true if `ancestor` is an ancestor of `node`.

# `ancestry_fields`
*macro* 

Defines the ancestry fields inside a `schema` block.

  * the ancestry column (`:string`, defaulting to the root value),
  * the depth cache column when `:cache_depth` is enabled,
  * the counter cache column when `:counter_cache` is enabled,
  * a virtual `:parent_id` field used by `AncestryEx.Changeset.cast_parent/1`.

Pass `parent_id: false` to skip the virtual field, for example when the
table still has a real `parent_id` column mapped in the schema.

# `arrange`

Arranges a flat list of nodes into a nested tree.

Returns a list of `{node, children}` tuples where `children` has the same
shape. Nodes whose parent is not in the list become top-level entries and
siblings keep the order they have in the input.

    Category.subtree(category)
    |> Category.ordered_by_ancestry()
    |> Repo.all()
    |> AncestryEx.arrange()
    #=> [{%Category{name: "Root"}, [{%Category{name: "Child"}, []}]}]

# `arrange_serializable`

Arranges nodes like `arrange/1` and converts each node into a map with a
`:children` key, ready to be encoded as JSON.

A function `(node, children) -> term` can be given to build each entry.

# `child_ancestry`

Ancestry value of the children of `node`. The node must be persisted.

# `child_of?`

Returns true if `node` is a child of `parent`.

# `depth`

Depth of `node` in its tree, roots have depth 0.

# `descendant_of?`

Returns true if `node` is a descendant of `ancestor`.

# `has_parent?`

Returns true if `node` has a parent.

# `in_subtree_of?`

Returns true if `node` is `ancestor` or one of its descendants.

# `indirect_of?`

Returns true if `node` is a descendant of `ancestor` but not one of its children.

# `parent_id`

Id of the parent of `node`, `nil` for a root.

# `parent_of?`

Returns true if `parent` is the parent of `node`.

# `path_ids`

Ids of the ancestors of `node` plus its own id, root first.

# `root?`

Returns true if `node` is a root.

# `root_id`

Id of the root of the tree `node` belongs to (its own id for a root).

# `root_of?`

Returns true if `root` is the root of the tree `node` belongs to.

# `sibling_of?`

Returns true if `a` and `b` are different nodes sharing the same parent.

# `sort_by_ancestry`

Sorts nodes so that every parent comes before its children (pre-order),
keeping the relative order of siblings.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
