View Source Graphing
Ecto traditionally is made for relational databases and hence relations between schemas is represented in such a way. A graph relation (edges) can be thought of as many-to-many relations in the relational database world. When it comes to representing graph relations in Ecto, that is exactly how it works in ArangoXEcto.
To use a graph relation you define an outgoing
and an incoming
relation on the two schemas,
for example the following will create a graph relationship between User -> UserPost -> Post
.
The UserPost
edge module will be created automatically.
defmodule MyApp.User do
schema "users" do
field :first_name, :string
field :last_name, :string
outgoing :posts, MyApp.Post
end
end
defmodule MyApp.Post do
schema "posts" do
field :title, :string
incoming :users, MyApp.User
end
end
Behind the scenes this works very similar to a many_to_many
relationships but has a few key
differences. As you may know, in a graph an edge can have multiple different node types that it
connects to/from. ArangoXEcto uses a special relation type that is based on the principal of
many-to-many but modified to allow such relationships.
For example, you may have a User
schema that you want to connect to Post
and Comment
. You
may not want to have two seperate relationships (e.g. :posts
and :comments
) and want to
combine it all under one (e.g. :content
). This is what the ArangoXEcto adapter enables. You can
read more about this type of relationship below.
Edge Modules
When in dynamic mode the ArangoXEcto adapter dynamically creates and manages edge collections. Each edge collection will be created as an Ecto schema when they are first used. This means that you don't need to create edge collections manually. When in static mode you need to define edges manually.
The edge module will be created under the closest common parent module of the passed modules
plus the Edges
alias. The order of the edge name will always be alphabetical to prevent
duplicate edges. For example, if the modules were MyApp.Apple.User
and MyApp.Apple.Banana.Post
then the edge would be created at MyApp.Apple.Edges.PostUser
. This assumes that the edge
collection name was generated and not explicitly defined, if it was PostUser
would be replaced
with the camel case of that collection name (i.e. address_people
would be AddressPeople
).
Multiple edge schemas
Creating a graph relation that has multiple schemas through the same edge is possible in ArangoXEcto. For example, take the following.
graph TD
User --> edge(UsersContent)
edge(UsersContent) --> Post
edge(UsersContent) --> Comment
Users can have posts or comments through the same edge. This works because edges can have multiple
from and to schemas. For example if we were to define the UsersContent
edge it would look like
this:
defmodule MyApp.Edges.UsersContent do
use ArangoXEcto.Edge,
from: User,
to: [Post, Comment]
schema "users_content" do
edge_fields()
end
end
We can then define our outgoing definition for the User schema like the following.
defmodule MyApp.User do
schema "users" do
field :first_name, :string
field :last_name, :string
outgoing(
:my_content,
%{
Post => &Post.changeset/2,
Comment => &Comment.changeset/2
},
edge: ArangoXEcto.Edges.UsersContents
)
end
end
The definition of outgoing here is slightly different from above. Instead of specifying a single
schema module we specify a map where the keys are schema modules and the values are a list of
field names to check if it exists in a result to match it with the module. This is an OR
condition, so if you had [:a, :b]
that would mean if a result had :a
it would match or if it
had :b
it would match. If a result had both it would match.
Notice that the edge here was explicitly defined. This is not necessary if you aren't doing anything custom on the edge and not including this would just generate an edge like the one above. But remember a migration is still required when in static mode.
You can also do the same with the ArangoXEcto.Schema.incoming/3
function.
Preloading
Due to how graph relations work the regular Ecto.Repo.preload/3
function will only return maps
instead of structs. If you want to preload the structs you can use the ArangoXEcto.preload/4
function.
iex> Repo.all(User) |> ArangoXEcto.preload(Repo, [:my_content])
[%User{id: 123, my_content: [%Post{...}, %Comment{...}]}]