-
Notifications
You must be signed in to change notification settings - Fork 276
Neo4j v3
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.
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.
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.
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
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.
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'
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.
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.
Implements like Active Records the following callback hooks:
- initialize
- find
- save
- create
- update
- destroy
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.
Support the Active Model validation, such as:
- validates :age, presence: true
- validates_uniqueness_of :name, :scope => :adult
The Neo4j::ActiveNode
module has a few ways to query:
Find returns one ruby object or nil if none was found.
# Example, find by id
Blog.find(4242)
All of the following result in enumerable results:
# Find all blog models
Blog.all
# Limit results
Blog.where(title: 'neo4j')
# Order
Person.where(age: 30).order(age: :desc).limit(5)
The ActiveNode.query_as and ActiveNode#query_as methods return Query objects which allow you to chain together query clauses and then determine how you'd like the results to be returned.
# Find all comments for any blog which have the world "pottery"
result = Blog.query_as(:blog).match("blog<-[:COMMENTS_ON]-(comment:Comment)").where(comment: {body: /pottery/}).pluck(:blog, :comment)
result.first.blog # First blog
result.first.comment # Comment
# Find all comment authors who's age is greater than 30
blog.query_as(:blog).match("blog<-[:COMMENTS_ON]-(:Comment)<-[:AUTHORED]-(author:Person)").where("author.age > 30").pluck(:author)
# You can even get start with basic query methods and then transfer to detailed querying
blog.where(title: 'neo4j').query_as(:blog).match("blog<--(comment:Comment)").where("comment.created_at >= '2014-07-09'").pluck(:comment)
You can also use the orm_adapter API, by calling #to_adapter on your class. See the API, https://github.com/ianwhite/orm_adapter
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)
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
Same as in neo4j-core
Same as the Neo4j-core API, see https://github.com/andreasronge/neo4j-core
blog.rels(dir: :outgoing, type: :comments)
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 (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
Start a QuickQuery by calling the qq
method on any ActiveModel class or instance.
Student.qq
#or
s = Student.all.first
s.qq
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)
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.
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+' })
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.
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.
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)
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.
At any time except after a return
or to_a
, call to_cypher
to see the Cypher query generated by your method chain.
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
See https://github.com/benjackson/devise-neo4j. Use the 2.0.0.alpha.X versions. See example application: https://github.com/andreasronge/rails-devise
See Rails 4 example: https://github.com/andreasronge/neo4j/tree/3.0/example/blog
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
WARNING: Much of the information in this wiki is out of date. We are in the process of moving things to readthedocs
- Project Introduction
- Neo4j::ActiveNode
- Neo4j::ActiveRel
- Search and Scope
- Validation, Uniqueness, and Case Sensitivity
- Indexing VS Legacy Indexing
- Optimized Methods
- Inheritance
- Core: Nodes & Rels
- Introduction
- Persistence
- Find : Lucene
- Relationships
- Third Party Gems & extensions
- Scaffolding & Generators
- HA Cluster