Skip to content

Commit

Permalink
has_list support for delete node from list. If a node is deleted it s…
Browse files Browse the repository at this point in the history
…hould automatically be removed from all lists it belongs to.

has_list now has a size counter [#79 state:resolved]  [#75 state:resolved]
  • Loading branch information
andreas committed Sep 28, 2009
1 parent 1c0ab81 commit d6b7c42
Show file tree
Hide file tree
Showing 7 changed files with 464 additions and 61 deletions.
8 changes: 2 additions & 6 deletions lib/neo4j.rb
Expand Up @@ -26,6 +26,7 @@
require 'neo4j/relationships/relationship_traverser'
require 'neo4j/relationships/node_traverser'
require 'neo4j/relationships/has_list'
require 'neo4j/relationships/list_node_mixin'

# neo4j
require 'neo4j/indexer' # this will replace neo4j/events
Expand All @@ -40,15 +41,10 @@



# TODO
# require 'extensions/reindexer'



#
# Set logger used by Neo4j
# Need to be done first since loading the required files might use this logger
#
require 'logger'
$NEO_LOGGER = Logger.new(STDOUT)
$NEO_LOGGER = Logger.new(STDOUT) # todo use a better logger
$NEO_LOGGER.level = Logger::WARN
54 changes: 27 additions & 27 deletions lib/neo4j/mixins/node_mixin.rb
Expand Up @@ -83,8 +83,8 @@ def set_property(name, value)
@internal_node.set_property(name, value)
end

if (name != 'classname') # do not want events on internal properties
self.class.indexer.on_property_changed(self, name) # TODO reuse the event_handler instead !
if (name != 'classname') # do not want events on internal properties
self.class.indexer.on_property_changed(self, name) # TODO reuse the event_handler instead !
Neo4j.event_handler.property_changed(self, name, old_value, value)
end
end
Expand Down Expand Up @@ -211,7 +211,7 @@ def neo_node_id
end


# Same as neo_node_id but returns a String intead of a Fixnum.
# Same as neo_node_id but returns a String instead of a Fixnum.
# Used by Ruby on Rails.
#
# :api: public
Expand Down Expand Up @@ -275,7 +275,7 @@ def classname=(value)
# The Neo4j::Relationships::RelationshipTraverser is an Enumerable that returns Neo4j::RelationshipMixin objects.
#
# ==== Returns
# A Neo4j::Relationships::RelationshipTraverser object
# A Neo4j::Relationships::RelationshipTraverser object
#
# ==== See Also
# * Neo4j::Relationships::RelationshipTraverser
Expand Down Expand Up @@ -323,7 +323,7 @@ def relationship(rel_name, dir=:outgoing)
# Returns true if there are one or more relationships from this node to other nodes
# with the given relationship.
# It will not return true only because the relationship is defined.
#
#
# ==== Parameters
# rel_name<#to_s>:: the key and value to be set
# dir:: optional default :outgoing (either, :outgoing, :incoming, :both)
Expand All @@ -342,17 +342,16 @@ def relationship?(rel_name, dir=:outgoing)

# :api: private
def _to_java_direction(dir)
java_dir =
case dir
when :outgoing
org.neo4j.api.core.Direction::OUTGOING
when :incoming
org.neo4j.api.core.Direction::INCOMING
when :both
org.neo4j.api.core.Direction::BOTH
else
raise "Unknown parameter: '#{dir}', only accept :outgoing, :incoming or :both"
end
case dir
when :outgoing
org.neo4j.api.core.Direction::OUTGOING
when :incoming
org.neo4j.api.core.Direction::INCOMING
when :both
org.neo4j.api.core.Direction::BOTH
else
raise "Unknown parameter: '#{dir}', only accept :outgoing, :incoming or :both"
end
end

# all creation of relationships uses this method
Expand Down Expand Up @@ -392,7 +391,7 @@ def traverse
# Updates the index for this node.
# This method will be automatically called when needed
# (a property changed or a relationship was created/deleted)
#
#
# @api private
def update_index
self.class.indexer.index(self)
Expand All @@ -403,7 +402,7 @@ def update_index


#
# Adds classmethods in the ClassMethods module
# Adds class methods in the ClassMethods module
#
def self.included(c)
# all subclasses share the same index, declared properties and index_updaters
Expand Down Expand Up @@ -581,7 +580,7 @@ def value_object
# index :name
# end
#
# :api: public
# :api: public
def index(*rel_type_props)
if rel_type_props.size == 2 and rel_type_props[1].kind_of?(Hash)
rel_type_props[1].each_pair do |key, value|
Expand Down Expand Up @@ -630,7 +629,7 @@ def index_relationship(rel_name, prop)

updater_clazz = self

rel_type = relationships_info[rel_name.to_sym][:type] # this or the other node we index ?
rel_type = relationships_info[rel_name.to_sym][:type] # this or the other node we index ?
rel_type ||= rel_name # if not defined (in a has_n) use the same name as the rel_name

# add index on the trigger class and connect it to the updater_clazz
Expand Down Expand Up @@ -679,17 +678,18 @@ 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
# Each list item class may (but not necessarily 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)
def has_list(rel_type, params = {})
counter = params[:counter] == true
module_eval(%Q{
def #{rel_type}(&block)
Relationships::HasList.new(self,'#{rel_type.to_s}', &block)
Relationships::HasList.new(self,'#{rel_type.to_s}',#{counter}, &block)
end}, __FILE__, __LINE__)
Neo4j.event_handler.add Relationships::HasList
relationships_info[rel_type] = Relationships::RelationshipInfo.new
end

Expand All @@ -701,9 +701,9 @@ def belongs_to_list(rel_type)
relationships_info[rel_type] = Relationships::RelationshipInfo.new
end


# Creates a new outgoing relationship.
#
#
# :api: private
def new_relationship(rel_name, internal_relationship)
relationships_info[rel_name.to_sym][:relationship].new(internal_relationship) # internal_relationship is a java neo object
Expand Down Expand Up @@ -736,7 +736,7 @@ def find(query=nil, &block)
#
# TODO: if the DynamicMixin is used it should return somthing more flexible
# since we do not know which property a class has.
#
#
# @api private
def create_value_class
# the name of the class we want to create
Expand Down
26 changes: 23 additions & 3 deletions lib/neo4j/relationships/has_list.rb
Expand Up @@ -5,12 +5,25 @@ class HasList
include Enumerable
extend Neo4j::TransactionalMixin

def initialize(node, type, &filter)
def initialize(node, list_name, counter, &filter)
@node = node
@type = type.to_s
@type = "_list_#{list_name}_#{node.neo_node_id}"
if (counter)
@counter_id = "_#{list_name}_size".to_sym
end
end

def size
@node[@counter_id] || 0
end


# called by the event handler
def self.on_node_deleted(node) #:nodoc:
# check if node is member of one or more lists
node.lists{|list_item| list_item.prev.next = list_item.next; list_item.size -= 1}
end

# Appends one node to the end of the list
#
# :api: public
Expand All @@ -29,9 +42,15 @@ def <<(other)
# the first node will be set
@node.relationships.outgoing(@type) << other
end
if @counter_id
@node[@counter_id] ||= 0
@node[@counter_id] += 1
end

self
end

# Returns true if the list is empty
# Returns true if the list is empty s
#
# :api: public
def empty?
Expand Down Expand Up @@ -61,6 +80,7 @@ def iterator
@node.internal_node.traverse(traverser_order, stop_evaluator, returnable_evaluator, types_and_dirs.to_java(:object)).iterator
end


transactional :empty?, :<<, :first
end

Expand Down
100 changes: 100 additions & 0 deletions lib/neo4j/relationships/list_node_mixin.rb
@@ -0,0 +1,100 @@
module Neo4j::NodeMixin

def list?(list_name)
regexp = Regexp.new "_list_#{list_name}"
relationships.both.find { |rel| regexp.match(rel.relationship_type.to_s) } != nil
end

def list(list_name, list_node = nil)
if list_node
list_id = "_list_#{list_name}_#{list_node.neo_node_id}"
add_list_item_methods(list_id, list_name, list_node)
else
list_items = []
lists(list_name) {|list_item| list_items << list_item}
list_items[0]
end
end


def lists(*list_names)
list_names.collect! {|n| n.to_sym}

relationships.both.inject({}) do |res, rel|
rel_type = rel.relationship_type.to_s
md = /_list_(\w*)_(\d*)/.match(rel_type)
next res if md.nil?
next res unless list_names.empty? || list_names.include?(md[1].to_sym)
res[rel_type] = [md[1], Neo4j.load(md[2].to_i)]
res
end.each_pair do |rel_id, list_name_and_head_node|
yield self.clone.add_list_item_methods(rel_id, list_name_and_head_node[0], list_name_and_head_node[1])
end
end


def add_list_item_methods(list_id, list_name, head_node) #:no_doc:
mod = Module.new do
define_method :head do
head_node
end

define_method :size do
head_node["_#{list_name}_size"] || 0
end

define_method :size= do |new_size|
head_node["_#{list_name}_size"] = new_size
end

define_method :next do
next_node = relationships.outgoing(list_id).nodes.first
return nil if next_node.nil?
next_node.add_list_item_methods(list_id, list_name, head_node)
next_node
end

define_method :next= do |new_next|
# does it have a next pointer ?
next_rel = relationships.outgoing(list_id).first
# delete the relationship if exists
next_rel.delete if (next_rel.nil?)
relationships.outgoing(list_id) << new_next
nil
end

define_method :prev do
prev_node = relationships.incoming(list_id).nodes.first
return nil if prev_node.nil?
prev_node.add_list_item_methods(list_id, list_name, head_node)
prev_node
end

define_method :prev= do |new_prev|
# does it have a next pointer ?
prev_rel = relationships.incoming(list_id).first
# delete the relationship if exists
prev_rel.delete if (prev_rel.nil?)
relationships.outgoing(list_id) << new_prev
nil
end
end

self.extend mod
end

def add_list_head_methods(list_name) # :nodoc:
prop_name = "_#{list_name}_size".to_sym
mod = Module.new do
define_method :size do
self[prop_name] || 0
end

define_method :size= do |new_size|
self[prop_name]=new_size
end
end
self.extend mod
end

end

0 comments on commit d6b7c42

Please sign in to comment.