View Source Dagex behaviour (dagex v3.0.1)
The Dagex
library is used to allow your business entities to participate in
directed, acyclic graphs backed by PostgreSQL's ltree
extenstion.
N.B. The callbacks defined in this module are automatically defined for you on
your Ecto models when you use Dagex
inside them.
Installation
See the instructions in the project's README to perform initial setup of the required database tables and functions.
Adding and manipulating a DAG for your business entity
Let's say your application deals with classifying different types of animals. You may wish to model a set of animals as follows (taken from a post by Kemal Erdogan):
graph TD
Animal --> Pet
Animal --> Livestock
Pet --> Cat
Pet --> Dog
Pet --> Sheep
Dog --> Doberman
Dog --> Bulldog
Livestock --> Dog
Livestock --> Sheep
Livestock --> Cow
Assuming you have configured Dagex as per the README, we can go
ahead and set up the Ecto model needed. First, run mix ecto.gen.migration add_animal_types
and then edit the resulting migration file:
defmodule DagexTest.Repo.Migrations.AddAnimalTypes do
def change do
create table("animal_types") do
add :name, :string, null: false
timestamps()
end
create index("animal_types", :name, unique: true)
Dagex.Migrations.setup_node_type("animal_types", "3.0.0")
end
end
Run migrations with mix ecto.migrate
and then create your model:
defmodule DagexTest.AnimalType do
use Ecto.Schema
use Dagex
schema "animal_types" do
field :name, :string
timestamps()
end
end
Now we can create the animal types and specify their associations in the DAG, and make queries about the graph itself:
iex> alias DagexTest.{AnimalType, Repo}
iex>
iex> # allows us to compare the contents of two lists independent of order
iex> import Assertions, only: [assert_lists_equal: 2]
iex>
iex> # Add a couple of nodes
iex> {:ok, animal} = %AnimalType{name: "Animal"} |> Repo.insert()
iex> {:ok, pet} = %AnimalType{name: "Pet"} |> Repo.insert()
iex>
iex> # Create an association between the nodes
iex> {:edge_created, _edge} = AnimalType.create_edge(animal, pet) |> Repo.dagex_update()
iex>
iex> # Add the remaining nodes and create the associations as per the graph
iex>
iex> {:ok, livestock} = %AnimalType{name: "Livestock"} |> Repo.insert()
iex> {:edge_created, _edge} = AnimalType.create_edge(animal, livestock) |> Repo.dagex_update()
iex>
iex> {:ok, cat} = %AnimalType{name: "Cat"} |> Repo.insert()
iex> {:edge_created, _edge} = AnimalType.create_edge(pet, cat) |> Repo.dagex_update()
iex>
iex> # Note that a node may have multiple parents
iex> {:ok, dog} = %AnimalType{name: "Dog"} |> Repo.insert()
iex> {:edge_created, _edge} = AnimalType.create_edge(pet, dog) |> Repo.dagex_update()
iex> {:edge_created, _edge} = AnimalType.create_edge(livestock, dog) |> Repo.dagex_update()
iex>
iex> {:ok, sheep} = %AnimalType{name: "Sheep"} |> Repo.insert()
iex> {:edge_created, _edge} = AnimalType.create_edge(pet, sheep) |> Repo.dagex_update()
iex> {:edge_created, _edge} = AnimalType.create_edge(livestock, sheep) |> Repo.dagex_update()
iex>
iex> {:ok, cow} = %AnimalType{name: "Cow"} |> Repo.insert()
iex> {:edge_created, _edge} = AnimalType.create_edge(livestock, cow) |> Repo.dagex_update()
iex>
iex> {:ok, doberman} = %AnimalType{name: "Doberman"} |> Repo.insert()
iex> {:edge_created, _edge} = AnimalType.create_edge(dog, doberman) |> Repo.dagex_update()
iex>
iex> {:ok, bulldog} = %AnimalType{name: "Bulldog"} |> Repo.insert()
iex> {:edge_created, _edge} = AnimalType.create_edge(dog, bulldog) |> Repo.dagex_update()
iex>
iex> # we can get the direct children of a node:
iex> children = AnimalType.children(animal) |> Repo.all()
iex> assert_lists_equal(children, [pet, livestock])
iex>
iex> # we can get the direct parents of a node
iex> parents = AnimalType.parents(dog) |> Repo.all()
iex> assert_lists_equal(parents, [pet, livestock])
iex>
iex> # we can get all of the descendants of a node
iex> descendants = AnimalType.descendants(pet) |> Repo.all()
iex> assert_lists_equal(descendants, [cat, dog, sheep, doberman, bulldog])
iex>
iex> # we can also get the descendants including the node itself
iex> descendants = AnimalType.with_descendants(pet) |> Repo.all()
iex> assert_lists_equal(descendants, [pet, cat, dog, sheep, doberman, bulldog])
iex>
iex> # we can get all ancestors of a node
iex> ancestors = AnimalType.ancestors(bulldog) |> Repo.all()
iex> assert_lists_equal(ancestors, [dog, pet, livestock, animal])
iex>
iex> # we can also get the ancestors including the node itself
iex> ancestors = AnimalType.with_ancestors(bulldog) |> Repo.all()
iex> assert_lists_equal(ancestors, [bulldog, dog, pet, livestock, animal])
iex>
iex> # we can determine if node A precedes (i.e. is an ancestor of) node B
iex> true = AnimalType.precedes?(pet, bulldog) |> Repo.exists?()
iex> false = AnimalType.precedes?(sheep, bulldog) |> Repo.exists?()
iex>
iex> # and we can determine if node A succeeds (i.e. is a descendant of) node B
iex> true = AnimalType.succeeds?(bulldog, pet) |> Repo.exists?()
iex> false = AnimalType.succeeds?(bulldog, sheep) |> Repo.exists?()
iex>
iex> # we can also get the possible paths between two nodes
iex> paths = AnimalType.all_paths(animal, sheep) |> Repo.dagex_paths()
iex> assert_lists_equal(paths, [[animal, pet, sheep], [animal, livestock, sheep]])
iex>
iex> # if we remove an edge
iex> {:edge_removed, _edge} = AnimalType.remove_edge(livestock, dog) |> Repo.dagex_update()
iex> # then
iex> false = AnimalType.succeeds?(bulldog, livestock) |> Repo.exists?()
iex> false = AnimalType.precedes?(livestock, bulldog) |> Repo.exists?()
iex>
iex> # and if we remove a node entirely
iex> Repo.delete!(livestock)
iex> # then
iex> [] = AnimalType.ancestors(cow) |> Repo.all()
iex> assert_lists_equal([dog, pet, animal], AnimalType.ancestors(bulldog) |> Repo.all())
Link to this section Summary
Callbacks
Returns a query that can be passed to your application's Ecto repository's
Dagex.Repo.dagex_paths/1
function in order to retrieve a list of the
possible paths between two nodes. Each path is itself a list starting with the
ancestor
node, ending with the descendant
node, and including each node in
the path between the two. Returns an empty list if no path exists between the
two nodes.
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities that are ancestors of the specified child entity.
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities that are children of the specified parent entity.
Returns a Dagex.Operations.CreateEdge
struct to be passed to
Dagex.Repo.dagex_update/1
that will attempt to create a new edge in
the implementing module's associated DAG.
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities that are descendants of the specified parent entity.
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities that are parents of the specified child entity.
Returns a query that selects ancestor only if ancestor is an ancestor of descendant.
Returns a Dagex.Operations.RemoveEdge
struct to be passed to
Dagex.Repo.dagex_update/1
that will attempt to remove the specified edge
from the implementing module's associated DAG.
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities of the type defined in this module that are at the top level of the DAG (i.e. have no other parents.)
Returns a query that selects descendant only if descendant is a descendant of ancestor.
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities that are ancestors of the specified child entity as well as the child entity itself.
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities that are descendants of the specified parent entity along with the parent entity itself.
Link to this section Callbacks
Specs
all_paths(ancestor :: Ecto.Schema.t(), descendant :: Ecto.Schema.t()) :: Ecto.Queryable.t()
Returns a query that can be passed to your application's Ecto repository's
Dagex.Repo.dagex_paths/1
function in order to retrieve a list of the
possible paths between two nodes. Each path is itself a list starting with the
ancestor
node, ending with the descendant
node, and including each node in
the path between the two. Returns an empty list if no path exists between the
two nodes.
Specs
ancestors(child :: Ecto.Schema.t()) :: Ecto.Queryable.t()
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities that are ancestors of the specified child entity.
Specs
children(parent :: Ecto.Schema.t()) :: Ecto.Queryable.t()
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities that are children of the specified parent entity.
Specs
create_edge(parent :: Ecto.Schema.t(), child :: Ecto.Schema.t()) :: Dagex.Operations.CreateEdge.t()
Returns a Dagex.Operations.CreateEdge
struct to be passed to
Dagex.Repo.dagex_update/1
that will attempt to create a new edge in
the implementing module's associated DAG.
Specs
descendants(parent :: Ecto.Schema.t()) :: Ecto.Queryable.t()
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities that are descendants of the specified parent entity.
Specs
parents(child :: Ecto.Schema.t()) :: Ecto.Queryable.t()
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities that are parents of the specified child entity.
Specs
precedes?(ancestor :: Ecto.Schema.t(), descendant :: Ecto.Schema.t()) :: Ecto.Queryable.t()
Returns a query that selects ancestor only if ancestor is an ancestor of descendant.
Specs
remove_edge(parent :: Ecto.Schema.t(), child :: Ecto.Schema.t()) :: Dagex.Operations.RemoveEdge.t()
Returns a Dagex.Operations.RemoveEdge
struct to be passed to
Dagex.Repo.dagex_update/1
that will attempt to remove the specified edge
from the implementing module's associated DAG.
Specs
roots() :: Ecto.Queryable.t()
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities of the type defined in this module that are at the top level of the DAG (i.e. have no other parents.)
Specs
succeeds?(descendant :: Ecto.Schema.t(), ancestor :: Ecto.Schema.t()) :: Ecto.Queryable.t()
Returns a query that selects descendant only if descendant is a descendant of ancestor.
Specs
with_ancestors(child :: Ecto.Schema.t()) :: Ecto.Queryable.t()
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities that are ancestors of the specified child entity as well as the child entity itself.
Specs
with_descendants(parent :: Ecto.Schema.t()) :: Ecto.Queryable.t()
Returns a query that can be passed to your application's Ecto repository to retrieve a list of entities that are descendants of the specified parent entity along with the parent entity itself.