EctoNeo4j
WARNING: WIP. This project is not production-ready (yet)
Goal
Ecto wrapper
Have a wrapper around Ecto in order to use Ecto.*-style functions.
With EctoNeo4j, it is possible to have a classic Ecto schema with its changeset and use
known Ecto Repo functions to persits data in a Neo4j database.
Use Neo4j driver easily
EctoNeo4j allows you to not care about the Bolt.Sips.conn() reuired in all functions.
for example, instead of writing:
conn = Bolt.Sips.conn()
Bolt.Sips.query(conn, "RETURN 1 AS num")
you can simply write:
MyRepo.query("RETURN 1 AS num")
Ultimate
The ultimate goal is to have also an extension of Ecto to manage relationship and have a kind of EctoCypher too.
Requirements
EctoNeo4j requires bolt_sips in order to work.bolt_sips should be defined as one of your project dependency.
More info bout bolt_sips: https://github.com/florinpatrascu/bolt_sips
Warning: about ids...
As you may know, it is strongly recommended to NOT rely on Neo4j internal ids, as they can be reaffected.
With Ecto.Schema, id can be managed automatically. EctoNeo4j allows to not change this way of working by
using a property called nodeId on created/updated nodes. This porprety is automically converted into id when
retrieving data from database.
Usage
Database config
See https://github.com/florinpatrascu/bolt_sips#usage
Your repo
Add a module with the folling code:
defmodule MyApp.MyRepo do
use EctoNeo4j.Repo
end
Add it to your supervision tree:
defmodule MyApp.Application do
use Application
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
# Define workers and child supervisors to be supervised
children = [
{MyApp.MyRepo, []}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
Considering the following module:
defmodule MyApp.Post do
use Ecto.Schema
import Ecto.Changeset
schema "Post" do
field(:title, :string)
field(:content, :string)
end
def changeset(test, params \\ %{}) do
test
|> cast(params, [:title, :desc])
|> validate_required([:content, :desc])
end
Usage - What's currently available
Considering the following module:
defmodule MyApp.Post do
use Ecto.Schema
import Ecto.Changeset
schema "Post" do
field(:title, :string)
field(:content, :string)
end
def changeset(test, params \\ %{}) do
test
|> cast(params, [:title, :desc])
|> validate_required([:content, :desc])
end
Inserting
data = %{title: "Hello World", content: "Neo4j is wonderful, isn't it?"}
changeset = MyApp.Post.changeset(%MyApp.Post{}, data)
MyRepo.insert(changeset)
insert!/2 is also available
Retrieving
One result
MyRepo.one(MyApp.Post)
# with a queryable
import Ecto.Query
query = from p in MyApp.Post, where: p.title == "Hello World", select: p.content
one!/2 is also available.
All results
MyRepo.all(MyApp.Post)
# with a queryable
import Ecto.Query
query = from p in MyApp.Post, where: p.title == "Hello World", select: p.content
MyRepo.all(query)
By id
MyRepo.get(MyApp.Post, 3)
get!/2 is also available.
Updating
MyRepo.get(MyApp.Post, 3)
|> MyApp.Post.changeset(%{title: "New title"})
|> MyRepo.update()
update!/2 is also available.
Deleting
MyRepo.get(MyApp.Post, 3)
|> MyRepo.delete()
delete!/2 is also available.
Raw cypher query
MyRepo.query("MATCH (p:Post {title: {title}})) RETURN p", %{title: "Searched"}
query!/2 is also available.
Queryable limitation
Ecto.Query is not yet fully compatible with EctoNeo4j as the translation work is in progress.
And in fact, some part have no meaning in Neo4j (join, etc.).
For now yo can use:
where:
- only with
and,or,==,>,>=,<,<=
- only with
- select
More to be covered soon.
Roadmap
- [ ] Cover all Repo callbacks
- [ ] Better and easier usage
- [ ] Cover as many Ecto.Query as possible
- [ ] Ecto schema extension
- [ ] Complete cypher integration
Can I contribute?
Yes, you can! Please, do! Just fork, commit and submit pull requests!