Arbor

Ecto nested-set and tree traversal using CTEs. Arbor uses a parent_id field and CTEs to create simple deep nested SQL hierarchies.

This is currently a WIP. Only children/1 and descendants/1 are currently implemented.

Installation

If available in Hex, the package can be installed as:

  1. Add arbor to your list of dependencies in mix.exs:

    def deps do
      [{:arbor, "~> 0.1.0"}]
    end
  2. Ensure arbor is started before your application:

    def application do
      [applications: [:arbor]]
    end

Usage

defmodule Comment do
  use Ecto.Schema
  # See config options below
  use Arbor.Tree, foreign_key_type: :binary_id

  schema "comments" do
    field :body, :string
    belongs_to :parent, Arbor.Comment

    timestamps
  end  
end

Descendants

Returns the entire descendant tree of a record.

Comment.descendants(my_comment)

Children

Returns the immediate children of a record.

Comment.children(my_comment)

Options

  • table_name - set the table name to use in CTEs
  • tree_name - set the name of the CTE
  • primary_key - defaults to field from Ecto’s @primary_key
  • primary_key_type - defaults to type from Ecto’s @primary_key
  • foreign_key - defauts to :parent_id
  • foreign_key_type - defaults to type from Ecto’s @primary_key
  • orphan_strategy - defaults to :nothing currently unimplemented

Contributing

You’ll need PostgreSQL installed and a user that can create and drop databases.

You can specify it with the environment variable ARBOR_DB_USER.

The mix test task will drop and create the database for each run.

mix test

TODO

  • Common tree operations
  • [x] descendants
  • [x] children
  • [ ] root
  • [ ] leafs
  • [ ] parent
  • [ ] siblings
  • [ ] ancestors
  • [ ] path (ancestors + target)
  • [ ] subtree (target + children)
  • [ ] delete (nilify, destroy, adopt)
  • [ ] move
  • [ ] branches (?)
  • [ ] Docs…?
  • [ ] depth selector, include depth or array size in CTE?
  • [ ] arbor.gen.migration SCHEMA_NAME FK_FIELD FK_TYPE
  • [ ] Move each query/operation into its own module to make Arbor.Tree less busy.
  • [ ] ID finders (?): root_ids, child_ids(target), etc…
  • [ ] Boolean functions: is_root?, has_children?, etc…
  • [ ] Support for multiple trees? The tree name could be prefixed on tree methods…

      use Arbor, my_tree: opts, my_other_tree: more_opts