Skip to content

Commit

Permalink
Added RDocs for Neo4j::NodeMixin#traverser method and examples of usa…
Browse files Browse the repository at this point in the history
…ge. Added a relationship_type method on the Neo4j::RelationMixin. [#15, #16, #17, #19]
  • Loading branch information
andreasronge committed Jan 13, 2009
1 parent eaf0d94 commit 14f2e95
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 30 deletions.
88 changes: 82 additions & 6 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ It uses two powerful and mature java libraries:
=== Status
* There are over 200 RSpecs.
* Has been tested with a simple rails application, used Neo4j.rb instead of ActiveRecord
* Has been load tested (loaded 18000 nodes and done queries/travesal in several threads.)
* Has been load tested (loaded 18000 nodes and done queries/traversal in several threads.)
* Has not been used in production yet (as far as I know).

=== Project information
Expand Down Expand Up @@ -660,17 +660,17 @@ Example:
If a Relationship class has not been specified for a relationship then any properties
can be set on the relationship. It has a default relationship class: Neo4j::DynamicRelation

=== Traversing relationships:
=== Traversing has_n Relationships

Each type of relationship has a method that returns an Enumerable object that enables you
to traverse that type of relationship.

For example the Person example above declares one relationship of type friends.
You can traverse all Person's friend by doing of depth 1:
You can traverse all Person's friend (depth 1 is default)

f.friends.each { |n| puts n }

Traversing friends of a specific depth.
It is also possible to traverse a relationship of an arbitrary depth.
Example finding all friends and friends friends.

f.friends.depth(2).each { ...}
Expand Down Expand Up @@ -711,7 +711,11 @@ A Neo4j::RelationMixin object represents a relationship between two nodes.
n1.relations[0].start_node # => n1
n1.relations[0].end_node # => n2

Relationships can also have properties just like a node.
A RelationMixin contains the relationship type which connects it connects two nodes with, example:

n1.relations[0].relationship_type # => :friends

Relationships can also have properties just like a node (NodeMixin).

=== Finding outgoing and incoming relationships

Expand Down Expand Up @@ -755,9 +759,81 @@ Let say we want to find who has my phone number and who consider me as a friend
me.relations.incoming(:phone_numbers).nodes # => people with my phone numbers

# who consider me as a friend
me.relations.incoming(:friend).nodes # => people with a friend relationship to me
me.relations.incoming(:friends).nodes # => people with a friend relationship to me

Remember that relationships are not symmetrical.
Notice there is also a otherway of finding nodes, see the Neo4j::NodeMixin#traverse method below.

=== Traversing Nodes

The Neo4j::NodeMixin also has a method for traversing nodes instead of relationships.
It works in a similar way to the Neo4j::NodeMixin#relations method.

Example:

me.relations.incoming(:friends).nodes # => people with a friend relationship to me
# is the same as
me.traverse.incoming(:friends) # => people with a friend relationship to me

Unlike the Neo4j::NodeMixin#relations the type(s) of relationships must be specified in the
incoming, outgoing or both method. Those three methods can take one or more relationship types parameters
if more then one type of relationship should be traversed.

The Neo4j::NodeMixin#traverse method it allows you to traverse nodes of any depth with a filter.

==== Traversing Nodes of Arbitrary Depth

The depth method allows you to specify how deep the traverse should be.
If not specified only one level traverse is done.

Example:

me.traverse.incoming(:friends).depth(4).each {} # => people with a friend relationship to me


==== Traversing Nodes with filter

It's possible to filter which nodes should be returned from the traverser object by
using the filter method.
The filter method takes a block with the parameter of type TraversalPosition which
contains information about current node, how many nodes has been returned, depth etc.
This information can be used in order to decide if the node should included in the traversal search result.
If the provided block returns true then the node will be included in the search result.

The TraversalPosition is a wrapper of java interface TraversalPosition, see
http://api.neo4j.org/current/org/neo4j/api/core/TraversalPosition.html

Example:

# A location contains a hierarchy of other locations
# Example region (asia) contains countries which contains cities etc...
class Location
include Neo4j::NodeMixin
has_n :contains
has_n :companies
property :name
index :name
end

# A company can exist in one or more locations
# A company can be local for a sub location (like a city) or global for a whole region (ie. europe).
class Company
include Neo4j::NodeMixin
property :name
end

# finding all companies in sweden
# notice how the tp (TraversalPosition) parameter is used in order to only
# include nodes included in a companies relationship.
sweden = Location.find('sweden')
traverser = sweden.traverse.outgoing(:contains, :companies).depth(:all).filter do |tp|
tp.last_relationship_traversed.relationship_type == :companies
end

traverser.each {|company| puts "Company name #{company.name}"}

Notice that the example above also shows that the traverser can traverse more then one
relationship type at the same type.

=== Transactions

Expand Down
50 changes: 28 additions & 22 deletions lib/neo4j/mixins/relation.rb
Original file line number Diff line number Diff line change
@@ -1,65 +1,71 @@
module Neo4j
module Neo4j


#
# A module that can be mixed in like a Neo4j::NodeMixin
# It wraps the Neo4j Relationship class.
#
module RelationMixin
extend TransactionalMixin

# Initialize the Relation object with specified java org.neo4j.api.core.Relationship object
# Expects at least one parameter.
#
# ==== Parameters
# param1<org.neo4j.api.core.Relationship>:: the internal java relationship object
#
# :api: public
def initialize(*args)
if args.length == 1 and args[0].kind_of?(org.neo4j.api.core.Relationship)
Transaction.run {init_with_rel(args[0])} unless Transaction.running?
init_with_rel(args[0]) if Transaction.running?
else
raise ArgumentError.new("This code should not be executed - remove todo")
Transaction.run {init_without_rel} unless Transaction.running?
init_without_rel if Transaction.running?
end
Transaction.run {init_with_rel(args[0])} unless Transaction.running?
init_with_rel(args[0]) if Transaction.running?

# must call super with no arguments so that chaining of initialize method will work
super()
end

#

# Inits this node with the specified java neo node
#
# :api: private
def init_with_rel(node)
@internal_r = node
self.classname = self.class.to_s unless @internal_r.hasProperty("classname")
$NEO_LOGGER.debug {"loading relation '#{self.class.to_s}' id #{@internal_r.getId()}"}
end


#
# Inits when no neo java node exists. Must create a new neo java node first.
#
def init_without_rel
@internal_r = Neo4j.instance.create_node
self.classname = self.class.to_s
self.class.fire_event RelationshipAddedEvent.new(self) #from_node, to_node, relation_name, relation_id
$NEO_LOGGER.debug {"created new node '#{self.class.to_s}' node id: #{@internal_node.getId()}"}
end

# :api: public
def end_node
id = @internal_r.getEndNode.getId
Neo4j.instance.find_node id
end

# :api: public
def start_node
id = @internal_r.getStartNode.getId
Neo4j.instance.find_node id
end

# :api: public
def other_node(node)
id = @internal_r.getOtherNode(node).getId
Neo4j.instance.find_node id
end

# Returns the neo relationship type that this relationship is used in.
# (see java API org.neo4j.api.core.Relationship#getType and org.neo4j.api.core.RelationshipType)
#
# ==== Returns
# Symbol
#
# :api: public
def relationship_type
@internal_r.get_type.name.to_sym
end


# Deletes the relationship between two nodes.
# Will fire a RelationshipDeletedEvent on the start_node class.
#
# :api: public
def delete
type = @internal_r.getType().name()
# start_node can be nil if it is a node that is not managed by Neo4j.rb
Expand Down
2 changes: 1 addition & 1 deletion lib/neo4j/relations/has_n.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ def each

# Creates a relationship instance between this and the other node.
# If a class for the relationship has not been specified it will be of type DynamicRelation.
# To set a relationship type see #Neo4j::relations
#
# :api: public
def new(other)
from, to = @node, other
from,to = to,from unless @info[:outgoing]
Expand Down
14 changes: 13 additions & 1 deletion test/neo4j/has_n_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ class Order
end


it "should return a RelationMixin of correct type" do
it "should return a RelationMixin of correct class" do
# given
c = Customer.new
o = Order.new
Expand All @@ -293,6 +293,18 @@ class Order
r.should be_kind_of(CustomerOrderRelation)
end

it "should return a RelationMixin of relationship type" do
# given
c = Customer.new
o = Order.new

# when
r = c.orders.new(o)

# then
r.relationship_type.should == :orders
end


it "should be possible to set a property on the returned relationship" do
# given
Expand Down
12 changes: 12 additions & 0 deletions test/neo4j/has_one_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ class Address
person.address.people.to_a.size.should == 1
person.address.people.to_a.should include(person)
end

it "should create a relationship with correct relationship type" do
# given
person = Person.new
address = Address.new {|a| a.city = 'malmoe'; a.road = 'my road'}

# when
dynamic_relation = address.people.new(person)

# then
dynamic_relation.relationship_type.should == :address
end

it "should should return the object using the has_one accessor" do
a = Address.new
Expand Down

0 comments on commit 14f2e95

Please sign in to comment.