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. Not all tree methods have been implemented, see TODOs below.
Installation
If available in Hex, the package can be installed as:
Add
arbor
to your list of dependencies inmix.exs
:def deps do [{:arbor, "~> 0.1.0"}] end
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
All methods return composable Ecto queries. For in depth examples see the tests
Roots
Returns root level records.
roots = Comment.roots
|> Repo.all
Siblings
Return the siblings of a record.
siblings = my_comment
|> Comment.siblings
|> Comment.order_by_popularity
|> Repo.all
ancestors
Returns the entire ancestor (parent’s parent’s parent, etc) path to the record, but not including the record.
descendants = my_comment
|> Comment.ancestors
|> Comment.order_by_inserted_at
|> Repo.all
Descendants
Returns the entire descendant tree of a record, but not including the record.
descendants = my_comment
|> Comment.descendants
|> Comment.order_by_inserted_at
|> Repo.all
Children
Returns the immediate children of a record.
children = my_comment
|> Comment.children
|> Repo.all
Parent
Returns the record’s parent.
parent = my_comment
|> Comment.parent
|> Repo.first
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
- [x] siblings
- [x] roots
- [x] parent
- [x] ancestors
- [ ] path (ancestors + target)
- [ ] subtree (target + children)
- [ ] delete (nilify, destroy, adopt)
- [ ] move
- [ ] leafs
- [ ] Document macros…
- [ ] depth selector on descendants and subtree… Add
AND cardinality(tree.ancestors) < XXX
to recursive section - [ ] 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