Mapping Nodes/Relationships to Ruby Classes

In this guide you will learn how to map ruby classes to neo4j relationships and nodes.

There is a complete example here of some of the examples below.

1 Creating a Model

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

require "rubygems"
require "neo4j"

class Person
  include Neo4j::NodeMixin

  # define Neo4j properties
  property :name, :salary, :age, :country

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

  # adds a Lucene index on the following properties
  index :name
  index :salary
  index :age
  index :country
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 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.

2 Creating a node

Creating a Person node instance:

person = Person.new

3 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'

3.1 Properties and the [] operator

Specifying which attributes should be available on a node is not required. 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. Declaring a property is not required in order to use an index on that property (as in Neo4j.rb version <= 0.4.6).

3.2 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)

4 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(:best_friends).outgoing.first.end_node # => other_node (if there is only one relationship of type 'best_friends' on person)

5 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)

5.1 has_list

The has_list class method is used to create an ordered and/or indexed list of nodes. It could be an alternative of using range queries with Lucene. It uses the timeline feature of the Java Neo4j API.

Example:

class Parent
  include Neo4j::NodeMixin
  has_list :children
end

# Make sure you have an transaction here
p = Parent.new
p.children[1992] = child1
p.children[1995] = child2
p.children[1998] = child3
# commit transaction here
p.children.between(1992..1998) => [child1, child2, child3]

Or you can just use it as a order list

p = Parent.create
# Make sure you have an transaction here
p.children << child1
p.children << child2
p.children << child3
p.children.size # == 3 # very efficient since it does not traverse in order to count all children

has_list is not available if you are using an online backup or ha cluster, since it does not yet work with those.

6 Finding Nodes and Queries

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

  1. by traversing the graph
  2. by using Lucene queries
  3. using the unique neo4j id (Neo4j::NodeMixin#neo_id).

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.

7 How do I sort the traversal result ?

  1. Using the Enumerable#sort_by
  2. Using lucene, see Indexing and Quering with Lucene
  3. Using Neo4j::NodeMixin#has_list, see above
  4. Implement your own “indexing” tree in neo4j

8 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