Neo4j::Rails Relationships

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

Saving Relationships

Creating relationships works similar to the Neo4j::NodeMixin and Neo4j::Node, except that it is kept in memory before the save method is called.

Example: Let’s say you have two nodes, n1 and n2. You can create a relationship between those two nodes of type friends of direction outgoing from n2 and incoming from n1. The relationship will have the type Neo4j::Rails::Relationship. This done with the following code (could also be done with Neo4j::Rails::Relationship.create):

n2.incoming(:friends) << n1
n2.save

Notice that the save method will save both the new Neo4j::Rails::Relationship object and any node that are related to the n2 node (and the same for those nodes !)

Traversing Nodes

The Neo4j::Rails::Model has the same traversal method as the Neo4j::Node, see Neo4j::Core-Traverse.
That means that you access nodes with the same method that creates relationship: outgoing, incoming and the has_n and has_one accessor methods. All these methods returns an object thats includes the Ruby Enumerable mixin (which means you can use each, collect, find etc).

Example:

m = Member.find(42)
m.outgoing(:posts).find_all{|post| post.keyword == 'ruby'}
# or
m.posts.find_all{|post| post.keyword == 'ruby'}

For more advanced traversals see Neo4j::Core-Traverse
Notice Traversing depth one may contains unpersisted relationship.

Accessing Relationships

The has_one and has_n method also generate accessors for getting the relationship objects.

m = Member.find(42)
m.posts_rels.to_a #=> an array of all type Neo4j::Rails::Relationship (or subclass of that).
m.avatar_rel #=> an object of  type Neo4j::Rails::Relationship (or subclass of that).
m.avatar_rel.rel_type #=> returns a symbol of the relationship type

You can also access any relationship using the rels and rel method, see Neo4j::Core-Nodes-Properties-Relationships

Validation

Validation of relationships and nodes for the friends relationship above will always be performed.
If a related nodes or relationship is not valid then an exception will be raised.

Finding All Relationships Between Two Nodes

The following example returns all relationships between a and b.

a.rels.to_other(b)

The relationship accessor method also allows you to find the relationships between two nodes using the to_other method (just like the #rels method).
To only find the friends relationships between those nodes:

# find all relationships object between p1 and p3, using the has_n accessor
p1.friends_rels.to_other(p3)

# not using the has_n accessor
a.rels(:both, Person.friends).to_other(b)

Mapping Nodes and Relationships

You can subclass both the Neo4j::Rails::Model and Neo4j::Rails::Relationship classes and specify the relationship between those.
Remember that you can also do this without any declaration at any time (just like for declaring properties or just setting them with the [] operator).

Let say you want to express the domain that an Actor has a Role to a Movie.
Example create three files and put them in the app/model folder.

app/models/role.rb

class Role < Neo4j::Rails::Relationship
  property :title, :character, :index => :exact # index both properties
end

app/models/actor.rb

class Actor < Neo4j::Rails::Model
  property :name, :index => :exact
  has_n(:acted_in).to(Movie).relationship(Role)
  index :name
end

app/models/movie.rb

class Movie < Neo4j::Rails::Model
  property :title, :year, :index => :exact

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

Now, when you create a relationship between actor and role it will be of type Role.
Example:

an_actor = Actor.find(42)
an_actor.acted_in << a_film
an_actor.acted_in_rels.first #=> a Role object

# or
role = an_actor.acted_in.create(:title => 'The Matrix')
role.character => 'neo'
role.save

You can now also navigate the incoming direction since we declared has_n(:actors).from(Actor, :acted_in)

matrix.actors.to_a #=> all the actors in that film

Neo4j.rb will prefix relationships when they are declared with to (has_n(:friends).to).
Example:

actor = Actor.create
actor.acted_in << movie1
actor.outgoing(:acted_in) << movie2
actor.outgoing(:acted_in) # only include movie2
actor.outgoing(Actor.acted_in) # only include movie1

TIP: By not specifying which class the relationship is associated to (using the to method above) gives you more freedom. If you instead just declared Actor.has_n(:acted_in) then an actor can have an acted_in relationship to different classes. Example @an_actor.acted_in << Neo4j::Rails::Model.new() << Thingy.create.

A note on creating and saving relationships

As seen in issue #186 and in the code it is, at least by now, a very bad approach to create relationships and link them to nodes after that.

Considering Actor, Movie, and Role objects from above, in Neo4j.rb version 2.0.1 this won’t work:

actor = Actor.first
movie = Movie.first
role = Role.new :title => "A cool film"
role.start_node = actor # raises an "undefined method `start_node='" exception
role.end_node = role # raises an "undefined method `end_node='" exception

Which is the normal behaviour for now because the start_node= and end_node= methods are protected.