Skip to content

Commit

Permalink
Cascade delete now works for both outgoing and incomig relationships …
Browse files Browse the repository at this point in the history
…and for has_one, has_n and has_list [#81 state:resolved]
  • Loading branch information
andreas committed Oct 12, 2009
1 parent 192ec17 commit 18ea53c
Show file tree
Hide file tree
Showing 5 changed files with 345 additions and 27 deletions.
81 changes: 78 additions & 3 deletions README.rdoc
Expand Up @@ -17,7 +17,7 @@ It uses two powerful and mature java libraries:
* Lucene (http://lucene.apache.org/java/docs/index.html) for quering and indexing.

=== Status
* There are over 300 RSpecs.
* There are over 400 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/traversal in several threads.)

Expand Down Expand Up @@ -679,7 +679,6 @@ Example:
company.employees.to_a # => [employee1, employee3]
company.employees.size # => 2


==== Memberships in lists

Each node in a list knows which lists it belongs to the next and previous item in the list
Expand All @@ -692,7 +691,83 @@ Example:

(The list method takes an optional extra parameter - the list node. Needed if one node is member of more then one list with the same name).

==== Traversing Relationships
=== Cascade delete

The has_one, has_n and has_list all support cascade delete.
There are two types of cascade delete - incoming and outgoing.
For an outgoing cascade delete the members (of the has_one/has_n/has_list) will all be deleted when the
'root' node is deleted. For incoming cascade the 'root' node will be deleted when all its member are deleted.

Example, outgoing

class Person
include Neo4j::NodeMixin
has_list :phone_nbr, :cascade_delete => :outgoing
end

p = Person.new
phone1 = new Node.new
phone1[:number] = '+46123456789'
p.phone_nbr << phone1
p.phone_nbr << phone2

p.delete

# then phone1 and phone2 node will also be deleted.

Example, incoming

class Phone
include Neo4j::NodeMixin
has_list :people # a list of people having this phone number
end

phone1 = Phone.new
p1 = Person.new
p2 = person.new
phone1.people << p1
phone1.people << p2

p1.delete
p2.delete

# then phone will be deleted


=== Finding all nodes

To find all nodes of a specific type use the all method.

Example

require 'neo4j/extensions/reindexer'

class Car
include Neo4j::Node
property :wheels
end

class Volvo < Car
end

v = Volvo.new
c = Car.new

Car.all # will return all relationships from the reference node to car obejcts
Volvo.all # will return the same as Car.all

To return nodes (just like the relationships method)

Car.all.nodes # => [c,v]
Volvo.all.nodes # => [v]


The reindexer extension that is used in the example above will for each created node create a relationship
from the index node (Neo4j.ref_node.relationships.outgoing(:index_node)) to that new node.
The all method use these relationships in order to return nodes of a certain class.
The update_index method also uses this all method in order to update index for all nodes of a specific class.

=== Traversing Relationships

Each type of relationship has a method that returns an Enumerable object that enables you
to traverse that type of relationship.
Expand Down
39 changes: 21 additions & 18 deletions lib/neo4j/mixins/node_mixin.rb
Expand Up @@ -251,7 +251,6 @@ def props

# Deletes this node.
# Invoking any methods on this node after delete() has returned is invalid and may lead to unspecified behavior.
# Runs in a new transaction if one is not already running.
#
# :api: public
def delete
Expand Down Expand Up @@ -657,15 +656,15 @@ def index_relationship(rel_name, prop)
# end
#
# :api: public
def has_one(rel_type)

def has_one(rel_type, params = {})
cascade_delete = cascade_delete_param(params)
module_eval(%Q{def #{rel_type}=(value)
r = Relationships::HasN.new(self,'#{rel_type.to_s}')
r = Relationships::HasN.new(self,'#{rel_type.to_s}', #{cascade_delete})
r << value
end}, __FILE__, __LINE__)

module_eval(%Q{def #{rel_type}
r = Relationships::HasN.new(self,'#{rel_type.to_s}')
r = Relationships::HasN.new(self,'#{rel_type.to_s}', #{cascade_delete})
r.to_a[0]
end}, __FILE__, __LINE__)
relationships_info[rel_type] = Relationships::RelationshipInfo.new
Expand All @@ -680,14 +679,28 @@ def has_one(rel_type)
# end
#
# :api: public
def has_n(rel_type)
def has_n(rel_type, params = {})
cascade_delete = cascade_delete_param(params)
module_eval(%Q{
def #{rel_type}(&block)
Relationships::HasN.new(self,'#{rel_type.to_s}', &block)
Relationships::HasN.new(self,'#{rel_type.to_s}', #{cascade_delete}, &block)
end}, __FILE__, __LINE__)
relationships_info[rel_type] = Relationships::RelationshipInfo.new
end

def cascade_delete_param(params)
cascade_delete = case params[:cascade_delete]
when nil
"nil"
when :outgoing
":_cascade_delete_outgoing"
when :incoming
":_cascade_delete_incoming"
else
raise "Expected either :outgoing or :incoming cascade delete parameter for has list"
end
return cascade_delete
end

# Specifies a relationship to a linked list of nodes.
# Each list item class may (but not necessarily use the belongs_to_list
Expand Down Expand Up @@ -739,17 +752,7 @@ def #{rel_type}(&block)
# :api: public
def has_list(rel_type, params = {})
counter = params[:counter] == true
cascade_delete = case params[:cascade_delete]
when nil
"nil"
when :outgoing
":_cascade_delete_outgoing"
when :incoming
":_cascade_delete_incoming"
else
raise "Expected either :outgoing or :incoming cascade delete parameter for has list"
end

cascade_delete = cascade_delete_param(params)
module_eval(%Q{
def #{rel_type}(&block)
Relationships::HasList.new(self,'#{rel_type.to_s}',#{counter},#{cascade_delete}, &block)
Expand Down
8 changes: 4 additions & 4 deletions lib/neo4j/relationships/has_list.rb
Expand Up @@ -51,10 +51,10 @@ def <<(other)
end

if @cascade_delete
# if cascade_delete_incoming only one node will be deleted, the node id is stored in that property
# if cascade_delete_outgoing all nodes will be deleted when
value = @cascade_delete == :_cascade_delete_outgoing ? true : @node.neo_node_id
new_rel.each {|rel| rel[@cascade_delete] = value}
# the @node.neo_node_id is only used for cascade_delete_incoming since that node will be deleted when all the list items has been deleted.
# if cascade_delete_outgoing all nodes will be deleted when the root node is deleted
# if cascade_delete_incoming then the root node will be deleted when all root nodes' outgoing nodes are deleted
new_rel.each {|rel| rel[@cascade_delete] = @node.neo_node_id}
end
if @counter_id
@node[@counter_id] ||= 0
Expand Down
24 changes: 23 additions & 1 deletion lib/neo4j/relationships/has_n.rb
Expand Up @@ -9,11 +9,13 @@ class HasN
include Enumerable
extend Neo4j::TransactionalMixin

def initialize(node, type, &filter)
def initialize(node, type, cascade_delete, &filter)
@node = node
@type = RelationshipType.instance(type)
@traverser = NodeTraverser.new(node.internal_node)
@info = node.class.relationships_info[type.to_sym]
@cascade_delete = cascade_delete

if @info[:outgoing]
@traverser.outgoing(type)
else
Expand All @@ -25,6 +27,18 @@ def initialize(node, type, &filter)
end


# called by the event handler
def self.on_node_deleted(node) #:nodoc:
if (@info[:outgoing])
node.relationship.outgoing.nodes.each {|n| n.delete}
elsif (@info[:incoming])
node.relationship.incoming.nodes.each {|n| n.delete}
else
node.relationship.both.nodes.each {|n| n.delete}
end
end


# Sets the depth of the traversal.
# Default is 1 if not specified.
#
Expand Down Expand Up @@ -97,6 +111,14 @@ def <<(other)
from, to = @node, other
from, to = to, from unless @info[:outgoing]
relationship = from._create_relationship(@type.name, to)

if @cascade_delete
# the @node.neo_node_id is only used for cascade_delete_incoming since that node will be deleted when all the list items has been deleted.
# if cascade_delete_outgoing all nodes will be deleted when the root node is deleted
# if cascade_delete_incoming then the root node will be deleted when all root nodes' outgoing nodes are deleted
relationship[@cascade_delete] = @node.neo_node_id
end

self
end

Expand Down

0 comments on commit 18ea53c

Please sign in to comment.