lib/cte.ex

defmodule CTE do
  @moduledoc """
  The Closure Table for Elixir strategy, CTE for short,
  is a simple and elegant way of storing and working with
  hierarchies. It involves storing all paths through a tree,
  not just those with a direct parent-child relationship. You
  may want to chose this model, over the [Nested Sets model](https://en.wikipedia.org/wiki/Nested_set_model),
  should you need referential integrity and to assign nodes to multiple trees.

  With CTE you can navigate through hierarchies using a
  simple [API](CTE.Adapter.html#functions), such as:
  finding the ascendants and descendants of a node, inserting
  and deleting nodes, moving entire sub-trees or print them
  as a digraph (.dot) file.

  Options available to most of the functions:

  - `:limit`, to limit the total number of nodes returned, when finding the ancestors or the descendants for nodes
  - `:itself`, accepting a boolean value. When `true`, the node used for finding its neighbors are returned as part of the results. Default: true
  - `:nodes`, accepting a boolean value. When `true`, the results are containing additional information about the nodes. Default: false


  ### Quick example.

  In this example the: `:nodes` attribute, will be a Schema i.e. `Post`, `TreePath`, etc!
  In our initial implementation, the nodes definitions must have at least the
  `:id`, as one of their properties. This caveat will be lifted
  in a later implementation.

  ......

  todo:  Update the docs


  Please check the docs, the tests, and the examples folder, for more details.
  """

  @type config :: Keyword.t()

  @type table :: String.t() | atom
  @type nodes :: map() | table
  @type paths :: [list()] | table
  @type repo :: Ecto.Repo | map()
  @type name :: String.t() | atom

  @type t :: %__MODULE__{
          nodes: nodes | nil,
          paths: paths | nil,
          repo: repo | nil,
          name: name | nil,
          options: map() | nil
        }
  defstruct [:nodes, :paths, :adapter, :repo, :name, :options]

  defmacro __using__(opts) do
    quote bind_quoted: [opts: opts] do
      @opts %CTE{
        nodes: Keyword.get(opts, :nodes, []),
        paths: Keyword.get(opts, :paths, []),
        repo: Keyword.get(opts, :repo, nil),
        options:
          Keyword.get(opts, :options, %{
            node: %{primary_key: :id, type: :integer},
            paths: %{
              ancestor: [type: :integer],
              descendant: [type: :integer]
            }
          })
      }

      def insert(leaf, ancestor, opts \\ [])

      def insert(leaf, ancestor, opts),
        do: CTE.Ecto.insert(leaf, ancestor, opts, @opts)

      def tree(leaf, opts \\ [])
      def tree(leaf, opts), do: CTE.Ecto.tree(leaf, opts, @opts)

      def ancestors(descendant, opts \\ [])

      def ancestors(descendant, opts),
        do: CTE.Ecto.ancestors(descendant, opts, @opts)

      def descendants(ancestor, opts \\ [])

      def descendants(ancestor, opts),
        do: CTE.Ecto.descendants(ancestor, opts, @opts)

      @doc """
      when limit: 1, the default value, then delete only the leafs, else the entire subtree
      """
      def delete(leaf, opts \\ [limit: 1])
      def delete(leaf, opts), do: CTE.Ecto.delete(leaf, opts, @opts)

      def move(leaf, ancestor, opts \\ [])

      def move(leaf, ancestor, opts),
        do: CTE.Ecto.move(leaf, ancestor, opts, @opts)
    end
  end
end