neo4j_model.cr - a Neo4j ORM for Crystal

Version Build Status License Gitter

The goal is a stable and full-featured Neo4j ORM for Crystal. Bolt only, no http or https (uses neo4j.cr). Inspired by ActiveNode/Neo4j.rb.

Features:

The provided association types do impose a convention on the relationship direction (biased toward active verbs), but I find it easier to think of relationships this way, rather than stick with Neo4j's required yet meaningless direction (the way ActiveNode does with the :in/:out parameter).

Installation

Add this to your application's shard.yml:

dependencies:
  neo4j_model:
    github: upspring/neo4j_model.cr

Usage

See specs for detailed usage.

To direct log output to a file:

Log.setup_from_env(backend: Log::IOBackend.new(File.open("log/production.log", "a"))
class Server
  include Neo4j::Model

  has_many Website, rel_type: :HOSTS # adds .websites
  has_many Website, name: :inactive_websites, rel_type: :USED_TO_HOST # adds .inactive_websites

  property name : String?
  property created_at : Time? = Time.utc
  property updated_at : Time? = Time.utc
end
class Website
  include Neo4j::Model

  belongs_to Server, rel_type: :HOSTS

  before_save :generate_api_key

  scope http2, ->{ where(supports_http2: true) }

  property _internal : Bool # properties starting with _ will not be synchronized with database

  property name : String?
  property api_key : String?
  property size_bytes : Integer?
  property size_updated_at : Time?
  property supports_http2 : Bool = false
  property nameservers : Array(String) = [] of String # defining it as an Array triggers the auto-serializer
  property some_hash : Hash(String, String)? # hashes ought to work too, but... not tested yet
  property created_at : Time? = Time.utc # will be set on create
  property updated_at : Time? = Time.utc # will be set on update

  def generate_api_key
    @api_key ||= UUID.random.to_s
    true # callbacks can return false/nil to abort/indicate failure (or truthy values to continue execution/indicate success)
  end
end

Including Neo4j::Model creates an embedded QueryProxy class that you can call directly as needed to run queries not yet supported by the query builder. For example, if Members are nested under both Organization and User and you need to check both, you could do this:

proxy = Member::QueryProxy.new("MATCH (o:Organization)-->(m:Member)<--(u:User)", "RETURN m").query_as(:m)
member = proxy.where("o.uuid = $o_uuid AND u.uuid = $u_uuid", o_uuid: org.uuid, u_uuid: user.uuid).limit(1).first?

However, now that we have some basic association chaining in place, you can also do it this way, which is slightly clearer:

member = org.members.users.where(uuid: user.uuid).return(member: :member)

Roadmap

For 2.0:

Future (help wanted!)

Contributing

The safest and easiest way to run the specs is via Docker (and safety is important, because the specs empty the database). docker-compose up to start the containers (and Ctrl-C to stop them when you're done). Then use the bin/guardian-docker script to start guardian, which will watch all the files in src/ and spec/ and run the test suite when any of them is modified.

  1. Fork it (https://github.com/upspring/neo4j_model.cr/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors