Hello,
I’m pleased and a bit proud to present you seraph a library to define schema and query Neo4j database.
Again?
Yes again…
Since I’ve started Elixir, I’ve seen here and there attempts to develop a library to use Neo4j nicely, and even an adapter to Ecto… released by me.
But nothing was satisfying enough and ecto_neo4j
was, and is still, a big disappointment. ecto
is relational database oriented, and there is more or less only one entity is this case: table. But when it comes to graph, you have two entities: node and relationship. Then all I’ve done with ecto_neo4j
was to force this two entities and graph concepts into a relational shape. Code became awful because of hacks everywhere, usage was limited and so restricted that you can’t model you graph database as you wish.
Then I drop this project and start seraph
.
seraph
is heavily inspired by ecto
because using ecto to work with database is joy. And I tried to bring this joy to Neo4j graph database.
It’s still the beginning, a LOT is missing but it’s a first (good?) step for a nice library to work with Neo4j with schema, nice query api, etc.
Hope you’ll enjoy it.
Testers and contributors
Because of the amount of work, help would be appreciated, so if you want to test, to add some features, write some docs, please do!
So, quickly, what you can do if you don’t want to read the docs.
Schema
a node schema is like this:
defmodule GraphApp.Blog.User do
use Seraph.Schema.Node
import Seraph.Changeset
alias GraphApp.Blog.User
alias GraphApp.Blog.Relationship
alias GraphApp.Blog.Relationship.NoProperties
node "User" do
property :firstName, :string
property :lastName, :string
property :email, :string
# You can define two relatinship with same type but pointing to different node
# without problem
outgoing_relationship("WROTE", GraphApp.Blog.Post, :posts, Relationship.Wrote,
cardinality: :many
)
outgoing_relationship(
"WROTE",
GraphApp.Blog.Comment,
:comments,
NoProperties.UserToComment.Wrote,
cardinality: :many
)
# A relationship can ends to the same node it starts
outgoing_relationship(
"FOLLOWS",
GraphApp.Blog.User,
:followed,
NoProperties.UserToUser.Follows,
cardinality: :many
)
end
# classic changeset
def changeset(%User{} = user, params \\ %{}) do
user
|> cast(params, [:firstName, :lastName, :email])
|> validate_required([:firstName, :lastName, :email])
end
end
a relationship schema:
defmodule GraphApp.Blog.Relationship.Wrote do
use Seraph.Schema.Relationship
@cardinality [outgoing: :one, incoming: :many]
relationship "WROTE" do
start_node GraphApp.Blog.User
end_node GraphApp.Blog.Post
property :when, :utc_datetime
end
end
Atomic operation with Repo.*
Each entity has its Repo.* functions:
- get
- get_by
- set
- create
…
Query DSL
And you can write nice queries with the query api.
import Seraph.Query
query = match [{u, User}],
where: [u.firstName == "John"],
return: [u]
GraphApp.Repo.all(query)
----
match([
{u, GraphApp.Blog.User, %{firstName: "Jim"}},
{u2, GraphApp.Blog.User, %{firstName: "Jane"}},
[{u}, [rel, GraphApp.Blog.Relationship.NoProperties.UserToUser.Follows], {u2}]
])
|> delete([rel])
|> GraphApp.Repo.execute()