Skip to content

Commit

Permalink
Merge branch 'master' into restful
Browse files Browse the repository at this point in the history
Conflicts:
	lib/neo4j/neo.rb
	lib/neo4j/relationships/has_n.rb
  • Loading branch information
Martin Kleppmann committed Jun 17, 2009
2 parents c3d9162 + 7e81ad0 commit 789289c
Show file tree
Hide file tree
Showing 13 changed files with 415 additions and 153 deletions.
3 changes: 2 additions & 1 deletion README.rdoc
Expand Up @@ -224,7 +224,8 @@ A relationship can be added at any time on any node.

Example:

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


=== Finding Nodes and Queries
Expand Down
12 changes: 6 additions & 6 deletions lib/neo4j/event_handler.rb
Expand Up @@ -34,14 +34,14 @@ def node_deleted(node)
@listeners.each {|li| li.on_node_deleted(node) if li.respond_to?(:on_node_deleted)}
end

def relationship_created(from_node, relationship_type, to_node)
return if @filter_classes.include?(from_node.class)
@listeners.each {|li| li.on_relationship_created(from_node, relationship_type, to_node) if li.respond_to?(:on_relationship_created)}
def relationship_created(relationship)
return if @filter_classes.include?(relationship.class)
@listeners.each {|li| li.on_relationship_created(relationship) if li.respond_to?(:on_relationship_created)}
end

def relationship_deleted(from_node, relationship_type, to_node)
return if @filter_classes.include?(from_node.class)
@listeners.each {|li| li.on_relationship_deleted(from_node, relationship_type, to_node) if li.respond_to?(:on_relationship_deleted)}
def relationship_deleted(relationship)
return if @filter_classes.include?(relationship.class)
@listeners.each {|li| li.on_relationship_deleted(relationship) if li.respond_to?(:on_relationship_deleted)}
end

def property_changed(node, key, old_value, new_value)
Expand Down
131 changes: 94 additions & 37 deletions lib/neo4j/extensions/tx_tracker.rb
@@ -1,27 +1,24 @@
module Neo4j


# The relationship class between TxNodes
class TxNodeRelationship
include Neo4j::RelationshipMixin
end

# This nodes listen for all events like node nodes created/deleted or
# if a property/relationship has changed. When a event is triggered it
#
class TxNodeList
include Neo4j::NodeMixin

has_list :tx_nodes

def initialize(*args)
super(args)

# its is configurable if we should track nodes or not
if (Config[:track_tx])
#Neo4j.instance.event_handler.add_listener(self)
end
end
has_list(:tx_nodes).relationship(TxNodeRelationship)

def on_node_created(node)
tx = TxNodeCreated.new
uuid = rand
uuid = create_uuid
node[:uuid] = uuid # TODO should use a better UUID
tx[:tracked_node_id] = node.neo_node_id
tx[:tracked_neo_id] = node.neo_node_id
tx[:created] = true
tx[:uuid] = uuid
tx_nodes << tx
Expand All @@ -47,33 +44,87 @@ def on_property_changed(node, key, old_value, new_value)
tx = TxNode.new
tx[:uuid] = node[:uuid]
tx[:property_changed] = true
tx[:tracked_node_id] = node.neo_node_id
tx[:tracked_neo_id] = node.neo_node_id
tx[:key] = key
tx[:old_value] = old_value
tx[:new_value] = new_value
tx_nodes << tx
end

def on_relationship_created(relationship)
tx = TxNode.new
uuid = create_uuid
tx[:uuid] = uuid
tx[:relationship_created] = true
tx[:tracked_neo_id] = relationship.neo_relationship_id
tx_nodes << tx
end


def on_relationship_deleted(relationship)
tx = TxNode.new
uuid = create_uuid
tx[:uuid] = uuid
tx[:relationship_deleted] = true
tx[:tracked_neo_id] = relationship.neo_relationship_id
tx[:relationship_type] = relationship.relationship_type.to_s
tx[:start_node_id] = relationship.start_node.neo_node_id
tx[:end_node_id] = relationship.end_node.neo_node_id

tx_nodes << tx
end

def create_uuid
rand(100000) # TODO a very bad UUID generator ...
end


def undo_tx
tx_node = tx_nodes.first
return if (tx_node.nil?)
tracked_node_id = tx_node[:tracked_node_id]

if (tx_node[:created])
node = Neo4j.load(tracked_node_id)
node.delete
elsif (tx_node[:deleted])
classname = tx_node[:tracked_classname]
clazz = classname.split("::").inject(Kernel) do |container, name|
container.const_get(name.to_s)
first_node = tx_nodes.first
return if first_node.nil?

nodes_to_undo = []
nodes_to_undo << first_node # always include the first one

# include all other nodes until we find a new transaction marker
curr_node = first_node
while (curr_node.relationship?(:tx_nodes, :outgoing)) do
curr_node = curr_node.relationship(:tx_nodes, :outgoing).end_node
break if curr_node[:tx_finished]
nodes_to_undo << curr_node
end

nodes_to_undo.each do |curr_node|
tracked_neo_id = curr_node[:tracked_neo_id] # TODO - remove
uuid = curr_node[:uuid]
if (curr_node[:created])
#node = Neo4j.load(tracked_neo_id)
node = Neo4j.load_uuid(uuid)
node.delete
elsif (curr_node[:deleted])
classname = curr_node[:tracked_classname]
clazz = classname.split("::").inject(Kernel) do |container, name|
container.const_get(name.to_s)
end
node = clazz.new
elsif (curr_node[:property_changed])
node = Neo4j.load(tracked_neo_id)
key = curr_node[:key]
old_value = curr_node[:old_value]
node[key] = old_value
elsif (curr_node[:relationship_created])
# delete created relationship
relationship = Neo4j.load_relationship(tracked_neo_id)
relationship.delete
elsif (curr_node[:relationship_deleted])
# recreate deleted relationship
type = curr_node[:relationship_type]
start_node_id = curr_node[:start_node_id]
end_node_id = curr_node[:end_node_id]
start_node = Neo4j.load(start_node_id)
end_node = Neo4j.load(end_node_id)
start_node.relationships.outgoing(type) << end_node
end
node = clazz.new
elsif (tx_node[:property_changed])
node = Neo4j.load(tracked_node_id)
key = tx_node[:key]
old_value = tx_node[:old_value]
node[key] = old_value
end
end

Expand Down Expand Up @@ -105,6 +156,8 @@ def self.instance
class TxNode
include Neo4j::NodeMixin

belongs_to_list(:tx_nodes).relationship(TxNodeRelationship)

property :uuid

def to_s
Expand All @@ -115,8 +168,10 @@ def to_s
class TxNodeCreated
include Neo4j::NodeMixin

belongs_to_list(:tx_nodes).relationship(TxNodeRelationship)

property :uuid
property :tracked_node_id
property :tracked_neo_id

index :uuid

Expand All @@ -130,7 +185,7 @@ def self.load_tx_tracker
Neo4j.event_handler.add_filter(TxNode)
Neo4j.event_handler.add_filter(TxNodeCreated)
Neo4j.event_handler.add(TxNodeList)

Neo4j.event_handler.add(TxNodeRelationship)
Neo4j::Transaction.run { TxNodeList.on_neo_started(Neo4j.instance) } if Neo4j.running?
end

Expand All @@ -144,15 +199,17 @@ def self.undo_tx
TxNodeList.instance.undo_tx
end


# Load a neo4j node given a cluster wide UUID (instead of neo_node_id)
#
# :api: public
def self.load_uuid(uuid)
txnode = TxNodeCreated.find(:uuid => uuid)
txnode = TxNodeCreated.find(:uuid => uuid).first
# does this node exist ?
id = txnode[:tracked_node_id]
id = txnode[:tracked_neo_id]
node = Neo4j.load(id)

# if it does not exist we need to create a new node
end

load_tx_tracker

end
70 changes: 68 additions & 2 deletions lib/neo4j/mixins/node.rb
Expand Up @@ -284,6 +284,9 @@ def classname=(value)
# Returns a Neo4j::Relationships::RelationshipTraverser object for accessing relationships from and to this node.
# The Neo4j::Relationships::RelationshipTraverser is an Enumerable that returns Neo4j::RelationshipMixin objects.
#
# ==== Returns
# A Neo4j::Relationships::RelationshipTraverser object
#
# ==== See Also
# * Neo4j::Relationships::RelationshipTraverser
# * Neo4j::RelationshipMixin
Expand All @@ -294,10 +297,35 @@ def classname=(value)
#
# :api: public
def relationships
Relationships::RelationshipTraverser.new(@internal_node)
Relationships::RelationshipTraverser.new(self)
end


# Returns a single relationship or nil if none available.
# If there are more then one relationship of the given type it will raise an exception.
#
# ==== Parameters
# type<#to_s>:: the key and value to be set
# dir:: optional default :outgoing (either, :outgoing, :incoming, :both)
#
# ==== Returns
# An object that mixin the Neo4j::RelationshipMixin representing the given relationship type
#
# ==== See Also
# * JavaDoc for http://api.neo4j.org/current/org/neo4j/api/core/Node.html#getSingleRelationship(org.neo4j.api.core.RelationshipType,%20org.neo4j.api.core.Direction)
# * Neo4j::RelationshipMixin
#
# ==== Example
#
# person_node.relationship(:address).end_node[:street]
# :api: public
def relationship(rel_name, dir=:outgoing)
java_dir = _to_java_direction(dir)
rel_type = Relationships::RelationshipType.instance(rel_name)
rel = @internal_node.getSingleRelationship(rel_type, java_dir)
Neo4j.load_relationship(rel.getId)
end

# Check if the given relationship exists
# Returns true if there are one or more relationships from this node to other nodes
# with the given relationship.
Expand All @@ -309,10 +337,18 @@ def relationships
#
# ==== Returns
# true if one or more relationships exists for the given rel_name and dir
# otherwise false
#
# :api: public
def relationship?(rel_name, dir=:outgoing)
type = Relationships::RelationshipType.instance(rel_name.to_s)
java_dir = _to_java_direction(dir)
@internal_node.hasRelationship(type, java_dir)
end


# :api: private
def _to_java_direction(dir)
java_dir =
case dir
when :outgoing
Expand All @@ -324,9 +360,25 @@ def relationship?(rel_name, dir=:outgoing)
else
raise "Unknown parameter: '#{dir}', only accept :outgoing, :incoming or :both"
end
@internal_node.hasRelationship(type, java_dir)
end

# all creation of relationships uses this method
# triggers event handling
# :api: private
def _create_relationship(type, to)
java_type = Relationships::RelationshipType.instance(type)
java_relationship = internal_node.createRelationshipTo(to.internal_node, java_type)

relationship =
if (self.class.relationships_info[type.to_sym].nil?)
Relationships::Relationship.new(java_relationship)
else
self.class.relationships_info[type.to_sym][:relationship].new(java_relationship)
end
Neo4j.event_handler.relationship_created(relationship)
self.class.indexer.on_relationship_created(self, type)
relationship
end

# Returns a Neo4j::Relationships::NodeTraverser object for traversing nodes from and to this node.
# The Neo4j::Relationships::NodeTraverser is an Enumerable that returns Neo4j::NodeMixin objects.
Expand Down Expand Up @@ -634,6 +686,12 @@ def #{rel_type}(&block)
end



# Specifies a relationship to a linked list of nodes.
# Each list item class may (but not neccessarly) use the belongs_to_list
# in order to specify which ruby class should be loaded when a list item is loaded.
#
# :api: public
def has_list(rel_type)
module_eval(%Q{
def #{rel_type}(&block)
Expand All @@ -643,6 +701,14 @@ def #{rel_type}(&block)
end


# Can be used together with the has_list to specify the ruby class of a list item.
#
# :api: public
def belongs_to_list(rel_type)
relationships_info[rel_type] = Relationships::RelationshipInfo.new
end


# Creates a new outgoing relationship.
#
# :api: private
Expand Down

0 comments on commit 789289c

Please sign in to comment.