View Source CTE.Adapter.Ecto (Closure Table v1.1.5)

A CTE Adapter implementation using an existing Ecto Repo for persisting the models.

The current implementation is depending on Ecto ~> 3.1; using Ecto.SubQuery!

For this implementation to work you'll have to provide two tables, and the name of the Repo used by your application:

  1. a table name containing the nodes. Having the id, as a the primary key
  2. a table name where the tree paths will be stores.
  3. the name of the Ecto.Repo, defined by your app

In a future version we will provide you with a convenient migration template to help you starting, but for now you must supply these tables.

For example, given you have the following Schemas for comments:

defmodule CT.Comment do
  use Ecto.Schema
  import Ecto.Changeset

  @timestamps_opts [type: :utc_datetime]

  schema "comments" do
    field :text, :string
    belongs_to :author, CT.Author

    timestamps()
  end
end

and a table used for storing the parent-child relationships

defmodule CT.TreePath do
  use Ecto.Schema
  import Ecto.Changeset
  alias CT.Comment

  @primary_key false

  schema "tree_paths" do
    belongs_to :parent_comment, Comment, foreign_key: :ancestor
    belongs_to :comment, Comment, foreign_key: :descendant
    field :depth, :integer, default: 0
  end
end

we can define the following module:

defmodule CT.MyCTE do
  use CTE,
  otp_app: :cte,
  adapter: CTE.Adapter.Ecto,
  repo: CT.Repo,
  nodes: CT.Comment,
  paths: CT.TreePath
end

We add our CTE Repo to the app's main supervision tree, like this:

defmodule CT.Application do
  use Application

  def start(_type, _args) do
    children = [
      CT.Repo,
      CT.MyCTE
    ]

    opts = [strategy: :one_for_one, name: CT.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

restart out app and then using IEx, we can start experimenting. Examples:

iex» CT.MyCTE.ancestors(9)
{:ok, [1, 4, 6]}

iex» CT.MyCTE.tree(6)
{:ok,
%{
nodes: %{
  6 => %CT.Comment{
    __meta__: #Ecto.Schema.Metadata<:loaded, "comments">,
    author: #Ecto.Association.NotLoaded<association :author is not loaded>,
    author_id: 2,
    id: 6,
    inserted_at: ~U[2019-07-21 01:10:35Z],
    text: "Everything is easier, than with the Nested Sets.",
    updated_at: ~U[2019-07-21 01:10:35Z]
  },
  8 => %CT.Comment{
    __meta__: #Ecto.Schema.Metadata<:loaded, "comments">,
    author: #Ecto.Association.NotLoaded<association :author is not loaded>,
    author_id: 1,
    id: 8,
    inserted_at: ~U[2019-07-21 01:10:35Z],
    text: "I’m sold! And I’ll use its Elixir implementation! <3",
    updated_at: ~U[2019-07-21 01:10:35Z]
  },
  9 => %CT.Comment{
    __meta__: #Ecto.Schema.Metadata<:loaded, "comments">,
    author: #Ecto.Association.NotLoaded<association :author is not loaded>,
    author_id: 3,
    id: 9,
    inserted_at: ~U[2019-07-21 01:10:35Z],
    text: "w⦿‿⦿t!",
    updated_at: ~U[2019-07-21 01:10:35Z]
  }
},
paths: [
        [6, 6, 0],
        [6, 8, 1],
        [8, 8, 0],
        [6, 9, 1],
        [9, 9, 0]
      ]
}}

Have fun!

Most of the functions implementing the CTE.Adapter behavior, will accept the following options:

  • :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

Summary

Functions

Retrieve the ancestors of a node

Returns a specification to start this module under a supervisor.

Delete a leaf or a subtree.

Retrieve the descendants of a node

Initializes the adapter supervision tree by returning the children and adapter metadata.

Insert a node under an existing ancestor

Move a subtree from one location to another.

start the Adapter server

Calculate and return a "tree" structure containing the paths and the nodes under the given leaf/node

Functions

Link to this function

ancestors(pid, descendant, opts)

View Source

Retrieve the ancestors of a node

Returns a specification to start this module under a supervisor.

See Supervisor.

Link to this function

delete(pid, leaf, opts \\ [limit: 1])

View Source

Delete a leaf or a subtree.

To delete a leaf node set the limit option to: 1, and in this particular case all the nodes that reference the leaf will be assigned to the leaf's immediate ancestor

If limit is 0, then the leaf and its descendants will be deleted

Link to this function

descendants(pid, ancestor, opts)

View Source

Retrieve the descendants of a node

Initializes the adapter supervision tree by returning the children and adapter metadata.

Link to this function

insert(pid, leaf, ancestor, opts)

View Source

Insert a node under an existing ancestor

Link to this function

move(pid, leaf, ancestor, opts)

View Source

Move a subtree from one location to another.

First, the subtree and its descendants are disconnected from its ancestors. And second, the subtree is inserted under the new parent (ancestor) and the subtree, including its descendants, is declared as descendants of all the new ancestors.

start the Adapter server

Calculate and return a "tree" structure containing the paths and the nodes under the given leaf/node