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

Changeset functions that keep the tree consistent.

Ecto has no model callbacks. Instead, these functions register
`Ecto.Changeset.prepare_changes/2` callbacks, which the repo runs inside the
same transaction as the insert, update or delete. They take care of:

  * resolving `parent_id` into the ancestry column (and rejecting cycles),
  * rewriting the ancestry of all descendants when a node moves (a single
    `UPDATE`),
  * keeping the depth and counter caches up to date,
  * touching ancestors (`touch: true`),
  * applying the orphan strategy when a node is deleted.

Saving with `Repo.insert/2`, `Repo.update/2` or `Repo.delete/2` without going
through these functions bypasses all of the above.

## Example

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

    # create a child, move it, delete its parent
    {:ok, child} = Repo.insert(Category.changeset(%Category{}, %{name: "B", parent_id: a.id}))
    {:ok, child} = child |> AncestryEx.Changeset.put_parent(other) |> Repo.update()
    {:ok, _} = a |> AncestryEx.Changeset.handle_orphans() |> Repo.delete()

# `cast_parent`

```elixir
@spec cast_parent(Ecto.Changeset.t()) :: Ecto.Changeset.t()
```

Casts the `parent_id` parameter into the ancestry column.

Reads `"parent_id"` (or `:parent_id`) from the changeset params, or from the
changes if `:parent_id` was already cast. A `nil` or empty value makes the
node a root. Otherwise the parent is loaded when the changeset is saved, and
the changeset fails with an error on `:parent_id` if it does not exist or if
it is the node itself or one of its descendants.

It also validates and tracks any change made directly to the ancestry column,
so it should be called in every changeset of the schema.

# `handle_orphans`

```elixir
@spec handle_orphans(Ecto.Changeset.t() | struct(), keyword()) :: Ecto.Changeset.t()
```

Prepares a node to be deleted with `Repo.delete/2`, applying the orphan
strategy to its descendants inside the delete transaction.

The strategy defaults to the `:orphan_strategy` option of the schema and can
be overridden with `orphan_strategy: strategy`. With `:restrict`, deleting a
node that has children returns `{:error, changeset}` with an error on the
ancestry column.

Accepts a changeset or a struct.

    category
    |> AncestryEx.Changeset.handle_orphans(orphan_strategy: :adopt)
    |> Repo.delete()

# `put_parent`

```elixir
@spec put_parent(Ecto.Changeset.t() | struct(), struct() | nil) :: Ecto.Changeset.t()
```

Sets the parent of the node to `parent` (a persisted node of the same
schema) or makes it a root when `parent` is `nil`.

Accepts a changeset or a struct.

---

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