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