Skip to content
auzengi edited this page Jul 9, 2014 · 57 revisions

The Neo4j V3 is a complete rewrite and will work on MRI since you can run it using either the Neo4j server or embedded API. The Neo4j embedded API is only available if running on JRuby.

Neo4j-core

Please read the neo4j-core README. The neo4j gem uses the neo4j-core gem. Neo4j gem provides an active model compliant api which means it will work great together with rails. Neo4j-core gem contains many useful methods and classes.

Usage from Ruby

Installation of Neo4j Server and start server:

rake neo4j:install[community-2.0.2]
rake neo4j:start

Example, Open a session to the neo4j server database (in IRB for example)

  Neo4j::Session.open(:server_db, "http://localhost:7474")

After you have created a session you can now use the database, see below.

Usage from JRuby

On JRuby you can access the database in two different ways: using the embedded db or the server db.

Example, Open a session to the neo4j embedded database (running in the same JVM)

  session = Neo4j::Session.open(:embedded_db, '/folder/db')
  session.start

Usage with a remote Server

Example of a rails config/application.rb file:

config.neo4j.session_options = { basic_auth: { username: 'foo', password: 'bar'} } 
config.neo4j.session_type = :server_db 
config.neo4j.session_path = 'http://localhost:7474'

All options from HTTParty are available. For more Information see http://rdoc.info/github/jnunemaker/httparty/HTTParty/ClassMethods

See https://gist.github.com/andreasronge/11189170 how to configure the Neo4j::Session with basic authentication from a none rails application.

Usage from heroku

Add a Neo4j db to your application:

heroku addons:add graphenedb

Example of a rails config/application.rb file:

config.neo4j.session_type = :server_db 
config.neo4j.session_path = ENV["GRAPHENEDB_URL"] || 'http://localhost:7474'

Usage from Rails

 rails new myapp -m http://andreasronge.github.com/neo4j/neo4j.rb -O
 cd myapp
 rake neo4j:install[community-2.0.2]
 rake neo4j:start

 rails generate scaffold User name:string email:string
 rails s
 open http://localhost:3000/users

Or manually modify the rails config file config/application.rb:

require 'neo4j/railtie'

module Blog
  class Application < Rails::Application
     # This is for embedded db, only available from JRuby
     #config.neo4j.session_type = :embedded_db # or server_db
     #config.neo4j.session_path = File.expand_path('neo4j-db', Rails.root) # or http://localhost:port
  end
end

You can skip Active Record by using the -O flag when generating the rails project.

Property

All properties for Neo4j::ActiveNode objects must be declared (unlike neo4j-core nodes). Properties are declared using the property method which is the same as attribute from the active_attr gem.

Example:

class Post
  include Neo4j::ActiveNode
  property :title
  property :text, default: 'bla bla bla'
  property :score, type: Integer, default: 0

  validates :title, :presence => true
  validates :score, numericality: { only_integer: true }

  index :title

  before_save do
    self.score = score * 100
  end

  has_n :friends
end

Properties can be indexed using the index method, see example above.

Callbacks

Implements like Active Records the following callback hooks:

  • initialize
  • find
  • save
  • create
  • update
  • destroy

created_at, updated_at

See http://neo4j.rubyforge.org/classes/Neo4j/Rails/Timestamps.html

class Blog
  include Neo4j::ActiveNode
  has_n(:comments, on_updated: set_timestamp, :on_created: set_timestamp)
  property :updated_at  # will automatically be set when model changes
end

User defined callbacks for relationships:

class Blog
  include Neo4j::ActiveNode
  has_n(:comments, on_updated :handle_update_rel, on_created: :handle_create_rel)
  property :updated_at  # will automatically be set when model changes

  def handle_update_rel(relationship, changed_props)
   # this method is called when a relationship is changed
  end

  def handle_create_rel(relationship, changed_props, other_node)
   # this method is called when a relationship is created
  end

end

The reason why we don't want active model compliant relationship objects is because the code is very complex, and maybe not much used.

Validation

Support the Active Model validation, such as:

  • validates :age, presence: true
  • validates_uniqueness_of :name, :scope => :adult

Query

The Neo4j::ActiveNode module has two query methods: find and all, which accepts the same arguments as Neo4j::Session#query in neo4j-core

all

# Find all blog models
Blog.all

# Find all blog models with the exact title: neo4j
Blog.all(conditions: title: "neo4j")

find

Find returns one ruby object or nil if none was found.

# find one blog node having the exact title 'neo4j.rb v3.0':
Blog.find(conditions: {title: "neo4j"})

# Example, find by id
Blog.find(4242)

Orm_Adapter

You can also use the orm_adapter API, by calling #to_adapter on your class. See the API, https://github.com/ianwhite/orm_adapter

Where, Order, Fluent API - NOT IMPLEMENTED

Not Implemented Yet

# Simple helper for the cypher where clause, http://docs.neo4j.org/chunked/stable/query-where.html
Blog.where(:n, 'n.name = {name} XOR (n.age < {age} AND n.name = "Tobias") OR NOT (n.name = "Tobias" OR
  n.name={name}', name: 'Peter', age: 30)

# Order
Blog.order(created_at: :desc)

# Chaining methods
Blog.where(:n, 'n.name = {name}', name: 'Andreas').order(:name, text: :asc)

Match

Not Implemented Yet Not sure we should impl. this:

# Return all blogs with comments, same as 'MATCH (n:Blog) -[:comments]->(comment) RETURN n' 
Blog.match(outgoing: :comments)

# Find all comments for blogs made by user andreas
Blog.comments(user: 'andreas')

# Find all comments made for blogs made last day
Blog.comments_rels(created_at: [Time.now - 1.day ... Time.now])
# TODO more possibilities to explore

Can be combined with where and sort

Cypher

Same as in neo4j-core

Relationship

Same as the Neo4j-core API, see https://github.com/andreasronge/neo4j-core

blog.rels(dir: :outgoing, type: :comments)

Declared Relationships

class Blog
  include Neo4j::ActiveNode
  has_n :comments
end
class Comment
  include Neo4j::ActiveNode
  has_one(:belongs_to_blog).from(:comments)
end

comment = Comment.create
blog = Blog.create
comment.belongs_to_blog = blog
blog.comments.to_a #=> includes comment

# or create a relationship object with properties
rel = Blog.comments.create(comment, since: 1994)

Similar to https://github.com/andreasronge/neo4j/wiki/Neo4j%3A%3ARails-Relationships

Notice, has_one relationships can be created and queries. Example

class Person
  include Neo4j::ActiveNode
  has_one(:address)
end

my_address = Address.create # a Neo4j::ActiveNode
Person.create(address: my_address)
Person.all(conditions: {address: my_address})

QuickQuery (work in progress)

QuickQuery (find in the query branch, not yet in Master) is intended as Cypher shorthand for quick query retrieval and property setting over defined models. It is friendly for ActiveRecord users who may not be Cypher experts and anyone who wants to return whole objects for inclusion in Views. Its methods generate queries using Neo4j::Core::Query, which is the preferred method of performing Cypher queries for maximum flexibility and power.

Limitations as of July 4, 2014

  • Does not work with Embedded Database
  • Does not return relationship objects

Getting Started

Start a QuickQuery by calling the qq method on any ActiveModel class or instance.

Student.qq
#or
s = Student.all.first
s.qq

Identifiers

All queries generated using QuickQuery will automatically assign Cypher identifiers to all nodes and relationships. Nodes start with :n1 and increment, relationships start with :r1. You can set your own identifiers at any time by passing a symbol along with your method.

Student.qq(:student)
#or
s = Student.first
s.qq(:student)

Matching properties

To match properties, use hashes of properties and values. If you want to do a regex match or use greater than, less than, etc,... you can use a string. Make sure you set your integer fields as Integer (or Date or something other than String) in your models or your Cypher comparisons won't work.

These can be passed during any traversal or by using the where method. If you omit an identifier in where, it will assume you mean the newest identifier in the chain (the "node on deck") and enter it for you.

Student.qq.where(age: 30)
#or
Student.qq.where(:n1, age: 30)
#or
Student.qq(:student).where(age: 30)
#or
Student.qq(:student).where(:student, age: 30)
#or
Student.qq.where('age > 29')
#or you can specify an existing identifier in a string
Student.qq.where('n1.age > 29')

where can be used at any place in your traversal if you want to use an identifier.

Relationship Traversal

QuickQuery uses relationships defined in models to generate chainable traversal methods. They expect your relationships to have explicitly defined source and destination classes:

class Student
  include Neo4j::ActiveNode
  property :name
  has_n(:lessons).to(Lesson)
end

class Lesson
  include Neo4j::ActiveNode
  property :name
  has_n(:students).from(Student, :lessons)
  has_n(:teachers).from(Teacher, :teaching)
end

class Teacher
  include Neo4j::ActiveNode
  property :name
  has_n(:teaching).to(Lesson, :teachers)
end

Creating relationships in this way allows QuickQuery to anticipate where your relationship is going and insert the appropriate Label into the query. If you do not want to define relationships, see the section on the match method.

Traversing a relationship is easy.

#all students, age greater than 29, taking history
Student.qq(:students).where('students.age > 29').lessons(name: 'history')
#starting from a single student, find all of their classmates who are older than 21
#this time, we don't need to specify an identifier in the string because it will see it's missing and add it for us
s.qq(:me).lessons.students('age > 29')

You can also specify properties of relationships by using rel_as to set an identifier and rel_where to set parameters.

#all students with grades equal to a+
Student.qq(:students).lessons(rel_as: :lesson_stats, rel_where: { grade: 'a+' })

Returning Objects

To return, call return, to_a, or to_a!. They all return an array of complete objects but have some subtle differences.

return accepts an optional symbol of the identifier to return as well as a boolean if you want distinct results. Very often, traversals overlap on nodes, so enabling distinct is helpful.

to_a also returns an array but does not accept a return symbol. Use it when you're in a hurry. It will automatically return the last object you worked with.

to_a! is identical to to_a except distinct is turned on.

#return all teachers who have students aged 21. Implicit return of teachers because it is the last link in the chain.
Student.qq(:student).where(:student, age: 21).lessons.teachers.return
#or
Student.qq(:student).where(:student, age: 21).lessons.teachers.to_a
#or
Student.qq(:student).where(:student, age: 21).lessons.teachers.to_a!

#return all lessons with students age 21 that are taught by Mr Smith, explicit return with distinct
Student.qq(:student).where(:student, age: 21).lessons(:l).teachers(name: 'Mr Smith').return(:l, true)

It is NOT possible to return relationships at the moment.

Setting properties

Use set_props to set specific properties that match criteria. Use set to completely overwrite ALL properties that match the criteria.

#mark all lessons with fewer than 20 students enrolled as having seats available
Lesson.qq.where('students_enrolled < 20').set_props(seats_available: true).return

You must call return to commit changes.

Skip, Limit, and Order

It is possible to skip or limit results to a certain number of records as well as set the order in which they are returned to you. At the moment, it is not possible to Skip and Limit but that will be fixed soon.

Pass skip or limit an integer to use them. Pass order a symbol of the property and a boolean true if you want it to descend. It ascends by default, per Neo4j default settings.

Student.qq.lessons.teachers.limit(10).order(:age, true)

Match

If you want to perform a Cypher match that is not part of your models or do something not explicitly supported, you can use the match method. Pass it a string of Cypher. Beware, though: once you do this, behavior may be unpredictable since QuickQuery will not be able to increment your nodes for you.

Student.qq.lessons.teachers(:t).match('t-[:likes]-o').return(:o)

If you find yourself doing this often, you should consider using Neo4j::Core::Query directly using the query_as method on any class or node.

to_cypher

At any time except after a return or to_a, call to_cypher to see the Cypher query generated by your method chain.

Inheritance

Index, properties and declared relationships (#has_n and #has_one) are inherited.

Example:

  class Vehicle
    include Neo4j::ActiveNode
    property :name, type: String
    index :name
  end

  class Car < Vehicle
    property :model
    index :model
  end

   bike = Vehicle.create(name: 'bike')
   volvo = Car.create(name: 'volvo', model: 'v60')
   saab = Car.create(name: 'saab', model: '900')

   Car.find(name: 'volvo') # => volvo
   Vehicle.find(name: 'volvo') # => volvo

   Car.find(model: '900') # => saab
   Vehicle.find(model: '900') # => saab

Neo4j with devise

See https://github.com/benjackson/devise-neo4j. Use the 2.0.0.alpha.X versions. See example application: https://github.com/andreasronge/rails-devise

Rails Example

See Rails 4 example: https://github.com/andreasronge/neo4j/tree/3.0/example/blog

Neo4j Core

The neo4j gem uses the neo4j-core gem, see https://github.com/andreasronge/neo4j-core See https://github.com/andreasronge/neo4j-core/tree/3.0

Clone this wiki locally