Closure Table v0.1.3 CTE View Source

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, should you need referential integrity and to assign nodes to multiple trees.

With CTE you can navigate through hierarchies using a simple API, 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.

Quick example.

For this example we're using the in-Memory Adapter. This Adapter is useful for prototyping or with data structures that can easily fit in memory; their persistence being taken care of by other components. For more involved use cases, CTE integrates with Ecto using a simple API.

When used from a module, the CTE expects the: :otp_app and :adapter attributes, to be defined. The :otp_app should point to an OTP application that might provide additional configuration. Equally so are the: :nodes and the :paths attributes. The :nodes attribute, in the case of the Memory Adapter, is a Map defining your nodes while the: :paths attribute, is a list containing the tree path between the nodes - a list of lists. For example:

defmodule CTM do
  use CTE,
    otp_app: :ct_empty,
    adapter: CTE.Adapter.Memory,
    nodes: %{
      1 => %{id: 1, author: "Olie", comment: "Is Closure Table better than the Nested Sets?"},
      2 => %{id: 2, author: "Rolie", comment: "It depends. Do you need referential integrity?"},
      3 => %{id: 3, author: "Polie", comment: "Yeah."}
    },
    paths: [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]]
end

When using the CTE.Adapter.Ecto, 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.

Add the CTM module to your main supervision tree:

defmodule CTM.Application do
  @moduledoc false

  use Application

  def start(_type, _args) do
    opts = [strategy: :one_for_one, name: CTM.Supervisor]

    Supervisor.start_link([CTM], opts)
  end
end

Using iex -S mix, for quickly experimenting with the CTE API:

  • find the descendants of comment #1

      iex» CTM.descendants(1)
      {:ok, [3, 2]}
  • find the ancestors

      iex» CTM.ancestors(2)
      {:ok, [1]}
    
      iex» CTM.ancestors(3)
      {:ok, [1]}
  • find the ancestors, with information about the node:

      iex» CTM.ancestors(2, nodes: true)
      {:ok,
      [
        %{
          author: "Olie",
          comment: "Is Closure Table better than the Nested Sets?",
          id: 1
        }
      ]}
  • move leafs/subtrees around. From being a child of comment #1, to becoming a child of comment #2, in the following example:

      iex» CTM.move(3, 2, limit: 1)
      :ok
      iex» CTM.descendants(2)
      {:ok, [3]}

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

Link to this section Summary

Link to this section Types

Link to this type

paths()

View Source
paths() :: [list()] | table()
Link to this type

repo()

View Source
repo() :: Ecto.Repo
Link to this type

t()

View Source
t() :: %CTE{
  adapter: any() | nil,
  nodes: nodes() | nil,
  paths: paths() | nil,
  repo: repo() | nil
}