README.md

# as_nested_set

**An [ecto](https://github.com/elixir-lang/ecto) based [Nested set model](https://en.wikipedia.org/wiki/Nested_set_model) implementation for database**

## Installation

Add as_nested_set to your list of dependencies in `mix.exs`:

      # use the stable version
      def deps do
        [{:as_nested_set, "~> 2.0", app: false}]
      end

      # use the latest version
      def deps do
        [{:as_nested_set, github: "https://github.com/secretworry/as_nested_set.git", app: false}]
      end

## Usage

To make use of `as_nested_set` your model has to have at least 4 fields: `id`, `lft`, `rgt` and `parent_id`. The name of those fields are configurable.

```elixir
defmodule AsNestedSet.TestRepo.Migrations.MigrateAll do
  use Ecto.Migration

  def change do
    create table(:taxons) do
      add :name, :string
      add :taxonomy_id, :id
      add :parent_id, :id
      add :lft, :integer
      add :rgt, :integer

      timestamps
    end

  end
end
```

Enable the nested set functionality by `use AsNestedSet` on your model

```elixir
defmodule AsNestedSet.Taxon do
  use AsNestedSet, scope: [:taxonomy_id]
  # ...
end
```

## Options

You can config the name of required field through attributes:

```elixir
defmodule AsNestedSet.Taxon do
  @right_column :right
  @left_column :left
  @node_id_column :node_id
  @parent_id_column :pid
  # ...
end
```

  * `@right_column`: column name for right boundary (default to `lft`, since left is a reserved keyword for Mysql)
  * `@left_column`: column name for left boundary (default to `rgt`, reserved too)
  * `@node_id_column`:  specifies the name for the node id column (default to `id`, i.e. the id of the model, change this if you want to have a different id, or you id field is different)
  * `@parent_id_column`: specifies the name for the parent id column (default to `parent_id`)

You can also pass following to modify its behavior:

```elixir
defmodule AsNestedSet.Taxon do
  use AsNestedSet, scope: [:taxonomy_id]
  # ...
end
```

  * `scope`: (required) a list of column names which restrict what is to be considered within the same tree(same scope).

## Model Operations

Once you have set up you model, you can then

Add a new node

```elixir
target = Repo.find!(Taxon, 1)
# add to left
%Taxon{name: "left", taxonomy_id: 1} |> Taxon.create(target, :left) |> Taxon.execute(TestRepo)
# add to right
%Taxon{name: "right", taxonomy_id: 1} |> Taxon.create(target, :right) |> Taxon.execute(TestRepo)
# add as first child
%Taxon{name: "child", taxonomy_id: 1} |> Taxon.create(target, :child) |> Taxon.execute(TestRepo)
# add as parent
%Taxon{name: "parent", taxonomy_id: 1} |> Taxon.create(target, :parent) |> Taxon.execute(TestRepo)

# add as root
%Taxon{name: "root", taxonomy_id: 1} |> Taxon.create(:root) |> Taxon.execute(TestRepo)
```

Remove a specified node and all its descendants

```elixir
target = Repo.find!(Taxon, 1)
Taxon.remove(target) |> Taxon.execute(TestRepo)
```

Query different nodes

```elixir

# find all roots
Taxon.roots(%{taxonomy_id: 1}) |> Taxon.execute(TestRepo)

# find all children of target
Taxon.children(target) |> Taxon.execute(TestRepo)

# find all the leaves for given scope
Taxon.leaves(%{taxonomy_id: 1}) |> Taxon.execute(TestRepo)

# find all descendants
Taxon.descendants(target) |> Taxon.execute(TestRepo)
# include self
Taxon.self_and_descendants(target) |> Taxon.execute(TestRepo)

# find all ancestors
Taxon.ancestors(target) |> Taxon.execute(TestRepo)

#find all siblings (self included)
Taxon.self_and_siblings(target) |> Taxon.execute(TestRepo)

```