Neo4j::wrapper introduction

Alex Burkhart edited this page Jun 25, 2013 · 9 revisions
Clone this wiki locally

You can use the neo4j-wrapper gem when you don't need rails but still need a mapping to Ruby classes for Neo4j nodes and relationships. The neo4j-wrapper is included in the neo4j gem but can also be used standalone, check the github project.

API

The neo4j-wrapper gem defines these mixins:

Creating a Model

The following example shows how to map a Neo4j node to a Ruby Person instance.

require "rubygems"
require "neo4j-wrapper"

class Person
  include Neo4j::NodeMixin

  # define Neo4j properties, with exact lucene index 
  property :name, :index => :exact
  property :salary, :index => :exact
  property :age, :index => :exact
  property :country, :index => :exact

  # define an one way relationship to any other node
  has_n :friends
end

Neo4j properties and relationships are declared using the 'property' and 'has_n'/'has_one' NodeMixin class method. Adding new types of properties and relationships can also be done without declaring those properties/relationships by using the operator '[]' on @Neo4j::NodeMixin@ and the '<<' on the @Neo4j::NodeTraverser@

By using the NodeMixin and by declaring properties and indices, all instances of the Person class can now be stored in the Neo4j node space and be retrieved/queried by traversing the node space or performing Lucene queries.

A Lucene index will be updated when the name or salary property changes.

Creating a node

Creating a Person node instance

  person = Person.new

Properties

Setting a property:

  person.name = 'kalle'
  person.salary  = 10000

You can also set this (or any property) when you create the node:

   person = Person.new :name => 'kalle', :salary => 10000, :foo => 'bar'

Properties and the [] operator

Notice that it is not required to specify which attributes should be available on a node. Any attributes can be set using the [] operator. Declared properties set an expectation, not an requirement. It can be used for documenting your model objects and catching typos.

Example:

  person['an_undefined_property'] = 'hello'

So, why declare properties in the class at all? By declaring a property in the class, you get the sexy dot notation. Also, when using Neo4j.rb from Ruby on Rails you sometimes need to declare properties on your models to get accepts_nested_attributes_for and ActiveModel::MassAssignmentSecurity working.

Properties as a hash

You can get all properties as an hash.

Example:

    puts "person #{person.props.inspect}"

You can also update any properties with an hash

Example:

    person.update( :name => 'foo', :age => 42, :colour => nil)

Relationships

Like properties, relationships do not have to be defined using has_n or has_one for a class. A relationship can be added at any time on any node.

Example:

  person.outgoing(:best_friends) << other_node
  person.rels(:outgoing, :best_friends).first.end_node # => other_node (if there is only one relationship of type 'best_friends' on person)

Mapping - one to many, many to many

Use to and from with has_n to specify which direction the generated method should traverse.

Example

class Role
  include Neo4j::RelationshipMixin
  # notice that neo4j relationships can also have properties
  property :name
end
class Actor
  include Neo4j::NodeMixin

  # The following line defines the acted_in relationship
  # using the following classes:
  # Actor[Node] --(Role[Relationship])--   Movie[Node]
  #
  has_n(:acted_in).to(Movie).relationship(Role)
end
class Director
  include Neo4j::NodeMixin
  property :name
  has_n(:directed).to(Movie)
end
class Movie
  include Neo4j::NodeMixin
  property :title
  property :year

  has_one(:director).from(Director, :directed)

  # defines a method for traversing incoming acted_in relationships from Actor
  has_n(:actors).from(Actor, :acted_in)
end

You can then do this:

lucas = Director.new :name => 'George Lucas'
star_wars_4 = Movie.new :title => 'Star Wars Episode IV: A New Hope', :year => 1977
star_wars_3 = Movie.new :title => "Star Wars Episode III: Revenge of the Sith", :year => 2005
lucas.directed << star_wars_3 << star_wars_4

lucas.directed.should include(star_wars_3, star_wars_4)
star_wars_3.director.should == lucas
star_wars_4.director.should == lucas

Which is same as:

lucas.outgoing("Movie#directed").should include(star_wars_3, star_wars_4)
star_wars_3.incoming("Movie#directed").should include(lucas)

Finding Nodes and Queries

There are three ways of finding/querying nodes in Neo4j:

  • by traversing the graph
  • by using Lucene queries
  • using the unique neo4j id (Neo4j::NodeMixin#neo_id).
  • Using Cypher

When doing a traversal one starts from a node and traverses one or more relationships (one or more levels deep). This start node can be either the reference node which is always found (Neo4j#ref_node) or by finding a start node from a Lucene query.

How do I sort the traversal result ?

  • Using the @Enumerable#sort_by@
  • Using lucene, see "Indexing and Quering with Lucene":lucene.html
  • Using @Neo4j::NodeMixin#has_list@, see above
  • Implement your own "indexing" tree in neo4j

Inheritance

It works as expected, example:

class Vehicle
  include Neo4::NodeMixin
  property :wheels
  index :wheels
end

class Car < Vehicle
end

Car.all  # => returns all cars
Vehicle.all # => returns all vehicles

Example

class Company
  include Neo4j::NodeMixin
  has_n(:employees)
end

class Person
  include Neo4j::NodeMixin
  property :name
  property :age, :size, :type => Fixnum, :index => :exact
  property :description, :index => :fulltext

  has_one(:best_friend)
  has_n(:employed_by).from(:employees)
end

Neo4j::Transaction.run do
  Person.new(:name => 'jimmy', :age => 35)
end

person = Person.find(:age => (10..42)).first

Neo4j::Transaction.run do
  person.best_friend = Person.new
  person.employed_by << Company.new(:name => "Foo ab")
end

# find by navigate incoming relationship
company = person.employed_by.find { |p| p[:name] == 'Foo ab' }
puts "Person #{person.name} employed by #{company[:name]}"
# navigate the outgoing relationship:
company.employees.each {|x| puts x.name}

Other Examples

There is a complete example here