-
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, index: :exact
property :text, default: 'bla bla bla'
property :score, type: Integer, default: 0
validates :title, :presence => true
validates :score, numericality: { only_integer: true }
before_save do
self.score = score * 100
end
has_n :friends
end
Properties can be indexed using the index
argument on the property method, see example above.
To declare a index on a property
class Person
include Neo4j::ActiveNode
property :name, index: :exact
end
Only exact index is currently possible.
You can declare that a property should have a unique value.
class Person
property :id_number, constraint: :unique # will raise an exception if id_number is not unique
end
Notice an unique validation is not enough to be 100% sure that a property is unique (because of concurrency issues, just like ActiveRecord).
Pass a property name as a symbol to the serialize
method if you want to save a hash or an array with mixed object types* to the database.
class Student
include Neo4j::ActiveNode
property :links
serialize :links
end
s = Student.create(links: { neo4j: 'http://www.neo4j.org', neotech: 'http://www.neotechnology.com' })
s.links
# => {"neo4j"=>"http://www.neo4j.org", "neotech"=>"http://www.neotechnology.com"}
s.links.class
# => Hash
Neo4j.rb serializes as JSON by default but pass it the constant Hash as a second parameter to serialize as YAML. Those coming from ActiveRecord will recognize this behavior, though Rails serializes as YAML by default.
*Neo4j allows you to save Ruby arrays to undefined or String types but their contents need to all be of the same type. You can do user.stuff = [1, 2, 3]
or user.stuff = ["beer, "pizza", "doritos"]
but not user.stuff = [1, "beer", "pizza"]
. If you wanted to do that, you could call serialize on your property in the model.
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
You can specify before and after callback methods on all declared relationships. There are some simple guidelines:
- Your callback methods must accept two parameters: the first for the "from" node, the second for the "to" node.
- If your before callback explicitly returns
false
, the relationship will not be created. -
has_n relationship setters will return false if a callback explicitly returns
false
, has_one will always return the object passed into the setter, so check for failure if you need to take action in your app
class Topic
include Neo4j::ActiveNode
property :last_post, type: DateTime
has_n(:posts, before: :check_poster, after: :set_topic_stats).from(Post, :topics)
has_one(:poster).to(User)
private
def check_poster(from, to)
return false if from.poster.nil?
end
def set_topic_stats(from, to)
self.poster = from.poster
self.last_post = DateTime.now
self.save
end
end
class Post
include Neo4j::ActiveNode
has_one(:poster, before: :check_post_privileges, after: :notify_friends).from(User)
def check_post_privileges(from, to)
return false if to.allowed_to_post == false
end
def notify_friends(from, to)
# call a method to notify the poster's friends of a new post
from.notify_friends(self)
end
end
# elsewhere in the app...
if !(@topic.posts << post)
#raise an error, notify someone, do something...
end
# but what if...
@post.poster = @user
# has_one callbacks do not return false, so check after setting
if @topic.poster.nil?
notify_admins_banned_user_is_being_shady
end
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)
For more information on Query objects, see the Neo4j::Core::Query documentation (LINK TO YARD DOCS NEEDED)
You can also use the orm_adapter API, by calling #to_adapter on your class. See the API, https://github.com/ianwhite/orm_adapter
Same as in neo4j-core, see Neo4j::Session.query
Relationships does not have to be declared in advanced before using them (just like properties). Example:
blog.rels(dir: :outgoing, type: :comments)
See Neo4j-core API
Will be changed !
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})
Also see the section on relationship callbacks from more info and examples.
QuickQuery is intended as Cypher shorthand for quick query retrieval and property setting over defined models and declared relationships. 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 11, 2014
- 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 who have lessons with grades equal to a+
Student.qq(:students).lessons(rel_as: :lesson_stats, rel_where: { grade: 'a+' }).return(:students)
Return options are specified by an optional return
method and either to_a
or to_a!
.
return is optional and accepts a 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 returns an array, as you'd probably guess. If you specified return parameters, it will respect those; otherwise, it will return the last object touched in your traversal.
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.to_a
#or for distinct results...
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).to_a
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. Call to_a
to commit.
#mark all lessons with fewer than 20 students enrolled as having seats available
Lesson.qq.where('students_enrolled < 20').set_props(seats_available: true).to_a
#mark all students enrolled in history with grades greater than 'b+' as honor students
Student.qq(:students).lessons(name: 'history 101', rel_where: { 'grade > b+' }.set_props(:students, honor_student: true).to_a
You must call to_a
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.
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.
#return 10 distinct teachers, skipping 5, sort by teacher age, who teach lessons that have students.
Student.qq.lessons.teachers.limit(10).skip(5).order(:age, true).to_a!
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).to_a
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