neo4j-wrapper 0.0.1-java
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +10 -0
- data/README.rdoc +64 -0
- data/lib/db/active_tx_log +1 -0
- data/lib/db/index/lucene/node/Person_exact/_0.fdt +0 -0
- data/lib/db/index/lucene/node/Person_exact/_0.fdx +0 -0
- data/lib/db/index/lucene/node/Person_exact/_0.fnm +1 -0
- data/lib/db/index/lucene/node/Person_exact/_0.frq +0 -0
- data/lib/db/index/lucene/node/Person_exact/_0.nrm +1 -0
- data/lib/db/index/lucene/node/Person_exact/_0.prx +0 -0
- data/lib/db/index/lucene/node/Person_exact/_0.tii +0 -0
- data/lib/db/index/lucene/node/Person_exact/_0.tis +0 -0
- data/lib/db/index/lucene/node/Person_exact/_1.fdt +0 -0
- data/lib/db/index/lucene/node/Person_exact/_1.fdx +0 -0
- data/lib/db/index/lucene/node/Person_exact/_1.fnm +1 -0
- data/lib/db/index/lucene/node/Person_exact/_1.frq +0 -0
- data/lib/db/index/lucene/node/Person_exact/_1.nrm +1 -0
- data/lib/db/index/lucene/node/Person_exact/_1.prx +0 -0
- data/lib/db/index/lucene/node/Person_exact/_1.tii +0 -0
- data/lib/db/index/lucene/node/Person_exact/_1.tis +0 -0
- data/lib/db/index/lucene/node/Person_exact/_2.fdt +0 -0
- data/lib/db/index/lucene/node/Person_exact/_2.fdx +0 -0
- data/lib/db/index/lucene/node/Person_exact/_2.fnm +1 -0
- data/lib/db/index/lucene/node/Person_exact/_2.frq +0 -0
- data/lib/db/index/lucene/node/Person_exact/_2.nrm +1 -0
- data/lib/db/index/lucene/node/Person_exact/_2.prx +0 -0
- data/lib/db/index/lucene/node/Person_exact/_2.tii +0 -0
- data/lib/db/index/lucene/node/Person_exact/_2.tis +0 -0
- data/lib/db/index/lucene/node/Person_exact/segments.gen +0 -0
- data/lib/db/index/lucene/node/Person_exact/segments_3 +0 -0
- data/lib/db/index/lucene-store.db +0 -0
- data/lib/db/index/lucene.log.active +0 -0
- data/lib/db/index.db +0 -0
- data/lib/db/messages.log +826 -0
- data/lib/db/neostore +0 -0
- data/lib/db/neostore.id +0 -0
- data/lib/db/neostore.nodestore.db +0 -0
- data/lib/db/neostore.nodestore.db.id +0 -0
- data/lib/db/neostore.propertystore.db +0 -0
- data/lib/db/neostore.propertystore.db.arrays +0 -0
- data/lib/db/neostore.propertystore.db.arrays.id +0 -0
- data/lib/db/neostore.propertystore.db.id +0 -0
- data/lib/db/neostore.propertystore.db.index +0 -0
- data/lib/db/neostore.propertystore.db.index.id +0 -0
- data/lib/db/neostore.propertystore.db.index.keys +0 -0
- data/lib/db/neostore.propertystore.db.index.keys.id +0 -0
- data/lib/db/neostore.propertystore.db.strings +0 -0
- data/lib/db/neostore.propertystore.db.strings.id +0 -0
- data/lib/db/neostore.relationshipstore.db +0 -0
- data/lib/db/neostore.relationshipstore.db.id +0 -0
- data/lib/db/neostore.relationshiptypestore.db +0 -0
- data/lib/db/neostore.relationshiptypestore.db.id +0 -0
- data/lib/db/neostore.relationshiptypestore.db.names +0 -0
- data/lib/db/neostore.relationshiptypestore.db.names.id +0 -0
- data/lib/db/nioneo_logical.log.active +0 -0
- data/lib/db/tm_tx_log.1 +0 -0
- data/lib/example.rb +34 -0
- data/lib/neo4j/identity_map.rb +109 -0
- data/lib/neo4j/node_mixin.rb +74 -0
- data/lib/neo4j/relationship_mixin.rb +62 -0
- data/lib/neo4j/type_converters/type_converters.rb +333 -0
- data/lib/neo4j-wrapper/class_methods.rb +27 -0
- data/lib/neo4j-wrapper/find.rb +65 -0
- data/lib/neo4j-wrapper/has_n/class_methods.rb +131 -0
- data/lib/neo4j-wrapper/has_n/decl_rel.rb +261 -0
- data/lib/neo4j-wrapper/has_n/instance_methods.rb +12 -0
- data/lib/neo4j-wrapper/has_n/nodes.rb +83 -0
- data/lib/neo4j-wrapper/node_mixin/class_methods.rb +53 -0
- data/lib/neo4j-wrapper/node_mixin/delegates.rb +140 -0
- data/lib/neo4j-wrapper/node_mixin/initialize.rb +43 -0
- data/lib/neo4j-wrapper/properties/class_methods.rb +150 -0
- data/lib/neo4j-wrapper/relationship_mixin/class_methods.rb +30 -0
- data/lib/neo4j-wrapper/relationship_mixin/delegates.rb +110 -0
- data/lib/neo4j-wrapper/relationship_mixin/initialize.rb +35 -0
- data/lib/neo4j-wrapper/rule/class_methods.rb +204 -0
- data/lib/neo4j-wrapper/rule/event_listener.rb +66 -0
- data/lib/neo4j-wrapper/rule/functions/count.rb +45 -0
- data/lib/neo4j-wrapper/rule/functions/function.rb +76 -0
- data/lib/neo4j-wrapper/rule/functions/sum.rb +31 -0
- data/lib/neo4j-wrapper/rule/instance_methods.rb +16 -0
- data/lib/neo4j-wrapper/rule/neo4j_core_ext/traverser.rb +29 -0
- data/lib/neo4j-wrapper/rule/rule.rb +134 -0
- data/lib/neo4j-wrapper/rule/rule_node.rb +205 -0
- data/lib/neo4j-wrapper/version.rb +5 -0
- data/lib/neo4j-wrapper/wrapper.rb +38 -0
- data/lib/neo4j-wrapper.rb +35 -0
- data/lib/test.rb +61 -0
- data/neo4j-wrapper.gemspec +31 -0
- metadata +162 -0
@@ -0,0 +1,204 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Wrapper
|
3
|
+
module Rule
|
4
|
+
|
5
|
+
|
6
|
+
# Allows you to group nodes by providing a rule.
|
7
|
+
#
|
8
|
+
# === Example, finding all nodes of a certain class
|
9
|
+
# Just add a rule without a code block, then all nodes of that class will be grouped under the given key (<tt>all</tt>
|
10
|
+
# for the example below).
|
11
|
+
#
|
12
|
+
# class Person
|
13
|
+
# include Neo4j::NodeMixin
|
14
|
+
# rule :all
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# Then you can get all the nodes of type Person (and siblings) by
|
18
|
+
# Person.all.each {|x| ...}
|
19
|
+
#
|
20
|
+
# === Example, finding all nodes with a given condition on a property
|
21
|
+
#
|
22
|
+
# class Person
|
23
|
+
# include Neo4j::NodeMixin
|
24
|
+
# property :age
|
25
|
+
# rule(:old) { age > 10 }
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# Now we can find all nodes with a property <tt>age</tt> above 10.
|
29
|
+
#
|
30
|
+
# === Chain Rules
|
31
|
+
#
|
32
|
+
# class NewsStory
|
33
|
+
# include Neo4j::NodeMixin
|
34
|
+
# has_n :readers
|
35
|
+
# rule(:featured) { |node| node[:featured] == true }
|
36
|
+
# rule(:young_readers) { !readers.find{|user| !user.young?}}
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# You can combine two rules. Let say you want to find all stories which are featured and has young readers:
|
40
|
+
# NewsStory.featured.young_readers.each {...}
|
41
|
+
#
|
42
|
+
# === Trigger Other Rules
|
43
|
+
# You can let one rule trigger another rule.
|
44
|
+
# Let say you have readers of some magazine and want to know if the magazine has old or young readers.
|
45
|
+
# So when a reader change from young to old you want to trigger all the magazine that he reads (a but stupid example)
|
46
|
+
#
|
47
|
+
# Example
|
48
|
+
# class Reader
|
49
|
+
# include Neo4j::NodeMixin
|
50
|
+
# property :age
|
51
|
+
# rule(:young, :triggers => :readers) { age < 15 }
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# class NewsStory
|
55
|
+
# include Neo4j::NodeMixin
|
56
|
+
# has_n :readers
|
57
|
+
# rule(:young_readers) { !readers.find{|user| !user.young?}}
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# === Performance Considerations
|
61
|
+
# If you have many rules and many updates this can be a bit slow.
|
62
|
+
# In order to speed it up somewhat you can use the raw java node object instead by providing an argument in your block.
|
63
|
+
#
|
64
|
+
# Example:
|
65
|
+
#
|
66
|
+
# class Person
|
67
|
+
# include Neo4j::NodeMixin
|
68
|
+
# property :age
|
69
|
+
# rule(:old) {|node| node[:age] > 10 }
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# === Thread Safe ?
|
73
|
+
# Yes, since operations are performed in an transaction. However you may get a deadlock exception:
|
74
|
+
# @see http://docs.neo4j.org/chunked/stable/transactions-deadlocks.html Transaction Deadlock
|
75
|
+
#
|
76
|
+
module ClassMethods
|
77
|
+
|
78
|
+
# Creates an rule node attached to the Neo4j.ref_node
|
79
|
+
# Can be used to rule all instances of a specific Ruby class.
|
80
|
+
#
|
81
|
+
# Example of usage:
|
82
|
+
# class Person
|
83
|
+
# include Neo4j::NodeMixin
|
84
|
+
# property :age
|
85
|
+
# rule :all
|
86
|
+
# rule :young { self[:age] < 10 }
|
87
|
+
# rule(:old, :functions => [Sum.new[:age]) { age > 20 }
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# p1 = Person.new :age => 5
|
91
|
+
# p2 = Person.new :age => 7
|
92
|
+
# p3 = Person.new :age => 12
|
93
|
+
# Neo4j::Transaction.finish
|
94
|
+
# Person.all # => [p1,p2,p3]
|
95
|
+
# Person.young # => [p1,p2]
|
96
|
+
# p1.young? # => true
|
97
|
+
# p1.sum(old, :age) # the some of the old people's age
|
98
|
+
#
|
99
|
+
def rule(rule_name, props = {}, &block)
|
100
|
+
singleton = class << self;
|
101
|
+
self;
|
102
|
+
end
|
103
|
+
|
104
|
+
# define class methods
|
105
|
+
singleton.send(:define_method, rule_name) do
|
106
|
+
rule_node = Rule.rule_node_for(self)
|
107
|
+
rule_node.traversal(rule_name)
|
108
|
+
end unless respond_to?(rule_name)
|
109
|
+
|
110
|
+
# define instance methods
|
111
|
+
self.send(:define_method, "#{rule_name}?") do
|
112
|
+
instance_eval &block
|
113
|
+
end
|
114
|
+
|
115
|
+
rule = Rule.add(self, rule_name, props, &block)
|
116
|
+
|
117
|
+
rule.functions && rule.functions.each do |func|
|
118
|
+
singleton.send(:define_method, func.class.function_name) do |r_name, *args|
|
119
|
+
rule_node = Rule.rule_node_for(self)
|
120
|
+
function_id = args.empty? ? "_classname" : args[0]
|
121
|
+
function = rule_node.find_function(r_name, func.class.function_name, function_id)
|
122
|
+
function.value(rule_node.rule_node, r_name)
|
123
|
+
end unless respond_to?(func.class.function_name)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def ref_node_for_class
|
128
|
+
Neo4j.ref_node #The reference node for a type falls back to the threadlocal ref node by default.
|
129
|
+
end
|
130
|
+
|
131
|
+
# Assigns the reference node for a class via a supplied block.
|
132
|
+
# Example of usage:
|
133
|
+
# class Person
|
134
|
+
# include Neo4j::NodeMixin
|
135
|
+
# ref_node { Neo4j.default_ref_node }
|
136
|
+
# end
|
137
|
+
#
|
138
|
+
def ref_node(&block)
|
139
|
+
singleton = class << self;
|
140
|
+
self;
|
141
|
+
end
|
142
|
+
singleton.send(:define_method, :ref_node_for_class) { block.call }
|
143
|
+
end
|
144
|
+
|
145
|
+
def inherit_rules_from(clazz)
|
146
|
+
Rule.inherit(clazz, self)
|
147
|
+
end
|
148
|
+
|
149
|
+
# This is typically used for RSpecs to clean up rule nodes created by the #rule method.
|
150
|
+
# It also remove all the rule class methods.
|
151
|
+
def delete_rules
|
152
|
+
singelton = class << self;
|
153
|
+
self;
|
154
|
+
end
|
155
|
+
rule_node = Rule.rule_node_for(self)
|
156
|
+
|
157
|
+
rule_node.rule_names.each { |rule_name| singelton.send(:remove_method, rule_name) }
|
158
|
+
rule_node.rules.clear
|
159
|
+
end
|
160
|
+
|
161
|
+
# Force to trigger the rules.
|
162
|
+
# You don't normally need that since it will be done automatically.
|
163
|
+
# This can be useful if you need to trigger rules on already existing nodes in the database.
|
164
|
+
# Can also be called from an migration.
|
165
|
+
#
|
166
|
+
def trigger_rules(node, *changes)
|
167
|
+
Rule.trigger_rules(node, *changes)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns a proc that will call add method on the given function
|
171
|
+
# Can be used in migration to trigger rules on already existing nodes.
|
172
|
+
# Parameter function_id is default to '_classname' which means that
|
173
|
+
# the function 'reacts' on changes on the property '_classname'.
|
174
|
+
# That property changes only when a node is created or deleted.
|
175
|
+
# Function using function_id '_classname' is typically used for counting number of nodes of a class.
|
176
|
+
def add_function_for(rule_name, function_name_or_class, function_id = '_classname')
|
177
|
+
function_for(:add, rule_name, function_name_or_class, function_id)
|
178
|
+
end
|
179
|
+
|
180
|
+
# See #add_function_for
|
181
|
+
# Calls the delete method on the function.
|
182
|
+
def delete_function_for(rule_name, function_name_or_class, function_id = '_classname')
|
183
|
+
function_for(:delete, rule_name, function_name_or_class, function_id)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns a proc that calls the given method on the given function.
|
187
|
+
def function_for(method, rule_name, function_name_or_class, function_id = '_classname')
|
188
|
+
function_name = function_name_or_class.is_a?(Symbol) ? function_name_or_class : function_name_or_class.function_name
|
189
|
+
rule_node = Rule.rule_node_for(self)
|
190
|
+
rule = rule_node.find_rule(rule_name)
|
191
|
+
rule_node_raw = rule_node.rule_node
|
192
|
+
|
193
|
+
function = rule_node.find_function(rule_name, function_name, function_id)
|
194
|
+
lambda do |node|
|
195
|
+
new_value = node[function_id]
|
196
|
+
function.send(method, rule.rule_name, rule_node_raw, new_value)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Wrapper
|
3
|
+
module Rule
|
4
|
+
class EventListener
|
5
|
+
class << self
|
6
|
+
# ----------------------------------------------------------------------------------------------------------------
|
7
|
+
# Event handling methods
|
8
|
+
# ----------------------------------------------------------------------------------------------------------------
|
9
|
+
|
10
|
+
def on_relationship_created(rel, *)
|
11
|
+
Rule.trigger_rules(rel._start_node) if Rule.trigger?(rel._start_node)
|
12
|
+
Rule.trigger_rules(rel._end_node) if Rule.trigger?(rel._end_node)
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_property_changed(node, *changes)
|
16
|
+
Rule.trigger_rules(node, *changes) if Rule.trigger?(node)
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_node_deleted(node, old_properties, deleted_relationship_set, deleted_identity_map)
|
20
|
+
# have we deleted a rule node ?
|
21
|
+
del_rule_node = Rule.find_rule_node(node)
|
22
|
+
del_rule_node && del_rule_node.clear_rule_node
|
23
|
+
return if del_rule_node
|
24
|
+
|
25
|
+
# do we have prop_aggregations for this
|
26
|
+
clazz = old_properties['_classname']
|
27
|
+
rule_node = Rule.rule_node_for(clazz)
|
28
|
+
return if rule_node.nil?
|
29
|
+
|
30
|
+
id = node.neo_id
|
31
|
+
rule_node.rules.each do |rule|
|
32
|
+
next if rule.functions.nil? || rule.bulk_update?
|
33
|
+
rule_name = rule.rule_name.to_s
|
34
|
+
|
35
|
+
# is the rule node deleted ?
|
36
|
+
deleted_rule_node = deleted_identity_map.get(rule_node.rule_node.neo_id)
|
37
|
+
next if deleted_rule_node
|
38
|
+
|
39
|
+
rule.functions.each do |function|
|
40
|
+
next unless deleted_relationship_set.contains?(id, rule_name)
|
41
|
+
previous_value = old_properties[function.function_id]
|
42
|
+
function.delete(rule_name, rule_node.rule_node, previous_value) if previous_value
|
43
|
+
end if rule.functions
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def classes_changed(changed_class_map)
|
48
|
+
changed_class_map.each_pair do |clazz, class_change|
|
49
|
+
Rule.bulk_trigger_rules(clazz, class_change, changed_class_map)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def on_neo4j_started(db)
|
54
|
+
if not Neo4j::Config[:enable_rules]
|
55
|
+
db.event_handler.remove(self)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
end
|
62
|
+
Neo4j.unstarted_db.event_handler.add(EventListener) unless Neo4j.read_only?
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Wrapper
|
3
|
+
module Rule
|
4
|
+
module Functions
|
5
|
+
|
6
|
+
# A function for counting number of nodes of a given class.
|
7
|
+
class Count < Function
|
8
|
+
def initialize
|
9
|
+
@property = '_classname'
|
10
|
+
end
|
11
|
+
|
12
|
+
def calculate?(changed_property)
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(rule_name, rule_node, _)
|
17
|
+
key = rule_node_property(rule_name)
|
18
|
+
rule_node[key] ||= 0
|
19
|
+
rule_node[key] -= 1
|
20
|
+
end
|
21
|
+
|
22
|
+
def add(rule_name, rule_node, _)
|
23
|
+
key = rule_node_property(rule_name)
|
24
|
+
rule_node[key] ||= 0
|
25
|
+
rule_node[key] += 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def update(*)
|
29
|
+
# we are only counting, not interested in property changes
|
30
|
+
end
|
31
|
+
|
32
|
+
def classes_changed(rule_name, rule_node, class_change)
|
33
|
+
key = rule_node_property(rule_name)
|
34
|
+
rule_node[key] ||= 0
|
35
|
+
rule_node[key] += class_change.net_change
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.function_name
|
39
|
+
:count
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Wrapper
|
3
|
+
module Rule
|
4
|
+
module Functions
|
5
|
+
|
6
|
+
# The base class of rule functions.
|
7
|
+
#
|
8
|
+
# You are expected to at least implement two methods:
|
9
|
+
# * update :: update the rule node value of this function
|
10
|
+
# * function_name :: the name of this function, the name of the generated method - A class method !
|
11
|
+
#
|
12
|
+
class Function
|
13
|
+
|
14
|
+
# Initialize the the function with a property which is usually the same as the function identity.
|
15
|
+
# See the #calculate? method how this property is used.
|
16
|
+
#
|
17
|
+
def initialize(property)
|
18
|
+
@property = property.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"Function #{self.class.function_name} function_id: #{function_id}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Decides if the function should be called are not
|
26
|
+
#
|
27
|
+
def calculate?(changed_property)
|
28
|
+
@property == changed_property
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# The identity of the function.
|
33
|
+
# Used to identify function.
|
34
|
+
#
|
35
|
+
# ==== Example
|
36
|
+
# Person.sum(:young, :age)
|
37
|
+
#
|
38
|
+
# In the example above the property :age is the used to identify which function will be called
|
39
|
+
# since there could be several sum method. In the example we want use the sum method that uses the :age property.
|
40
|
+
#
|
41
|
+
def function_id
|
42
|
+
@property
|
43
|
+
end
|
44
|
+
|
45
|
+
# The value of the rule
|
46
|
+
def value(rule_node, rule_name)
|
47
|
+
key = rule_node_property(rule_name)
|
48
|
+
rule_node[key] || 0
|
49
|
+
end
|
50
|
+
|
51
|
+
# Called when a node is removed from a rule group
|
52
|
+
# Default is calling update method which is expected to be implemented in a subclass
|
53
|
+
def delete(rule_name, rule_node, old_value)
|
54
|
+
update(rule_name, rule_node, old_value, nil)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Called when a node is added to a rule group
|
58
|
+
# Default is calling update method which is expected to be implemented in a subclass
|
59
|
+
def add(rule_name, rule_node, new_value)
|
60
|
+
update(rule_name, rule_node, nil, new_value)
|
61
|
+
end
|
62
|
+
|
63
|
+
# the name of the property that holds the value of the function
|
64
|
+
def rule_node_property(rule_name)
|
65
|
+
self.class.rule_node_property(self.class.function_name, rule_name, @property)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.rule_node_property(function_name, rule_name, prop)
|
69
|
+
"_#{function_name}_#{rule_name}_#{prop}"
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Wrapper
|
3
|
+
module Rule
|
4
|
+
module Functions
|
5
|
+
|
6
|
+
class Sum < Function
|
7
|
+
# Updates the function's value.
|
8
|
+
# Called after the transactions commits and a property has been changed on a node.
|
9
|
+
#
|
10
|
+
# ==== Arguments
|
11
|
+
# * rule_name :: the name of the rule group
|
12
|
+
# * rule_node :: the node which contains the value of this function
|
13
|
+
# * old_value new value :: the changed value of the property (when the transaction commits)
|
14
|
+
def update(rule_name, rule_node, old_value, new_value)
|
15
|
+
key = rule_node_property(rule_name)
|
16
|
+
rule_node[key] ||= 0
|
17
|
+
old_value ||= 0
|
18
|
+
new_value ||= 0
|
19
|
+
rule_node[key] += new_value - old_value
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.function_name
|
23
|
+
:sum
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Wrapper
|
3
|
+
module Rule
|
4
|
+
module InstanceMethods
|
5
|
+
# Trigger rules.
|
6
|
+
# You don't normally need to call this method (except in Migration) since
|
7
|
+
# it will be triggered automatically by the Neo4j::Wrapper::Rule::Rule
|
8
|
+
# @see Neo4j::Wrapper::Rule::ClassMethods
|
9
|
+
def trigger_rules
|
10
|
+
self.class.trigger_rules(self)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Core
|
3
|
+
module Traversal
|
4
|
+
# Extends the Neo4j::Core Traverser in order to add rule traversal methods.
|
5
|
+
class Traverser
|
6
|
+
def filter_method(name, &proc)
|
7
|
+
# add method name
|
8
|
+
singelton = class << self;
|
9
|
+
self;
|
10
|
+
end
|
11
|
+
singelton.send(:define_method, name) { filter &proc }
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def functions_method(func, rule_node, rule_name)
|
16
|
+
singelton = class << self;
|
17
|
+
self;
|
18
|
+
end
|
19
|
+
singelton.send(:define_method, func.class.function_name) do |*args|
|
20
|
+
function_id = args.empty? ? "_classname" : args[0]
|
21
|
+
function = rule_node.find_function(rule_name, func.class.function_name, function_id)
|
22
|
+
function.value(rule_node.rule_node, rule_name)
|
23
|
+
end
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Wrapper
|
3
|
+
module Rule
|
4
|
+
|
5
|
+
|
6
|
+
# Holds all defined rules added by the Neo4j::Rule::ClassMethods#rule method.
|
7
|
+
#
|
8
|
+
# @see Neo4j::Wrapper::Rule::ClassMethods
|
9
|
+
class Rule
|
10
|
+
|
11
|
+
attr_reader :rule_name, :filter, :triggers, :functions
|
12
|
+
|
13
|
+
def initialize(rule_name, props, &block)
|
14
|
+
@rule_name = rule_name
|
15
|
+
@triggers = props[:triggers]
|
16
|
+
@functions = props[:functions]
|
17
|
+
@triggers = [@triggers] if @triggers && !@triggers.respond_to?(:each)
|
18
|
+
@functions = [@functions] if @functions && !@functions.respond_to?(:each)
|
19
|
+
@filter = block
|
20
|
+
@bulk_update = !@functions.nil? && @functions.size == 1 && @functions.first.class.function_name == :count && @filter.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"Rule #{rule_name} props=#{props.inspect}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_function(function_name, function_id)
|
28
|
+
function_id = function_id.to_s
|
29
|
+
@functions && @functions.find { |f| f.function_id == function_id && f.class.function_name == function_name }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Reconstruct the properties given when created this rule
|
33
|
+
# Needed when inheriting a rule and we want to duplicate a rule
|
34
|
+
def props
|
35
|
+
props = {}
|
36
|
+
props[:triggers] = @triggers if @triggers
|
37
|
+
props[:functions] = @functions if @functions
|
38
|
+
props
|
39
|
+
end
|
40
|
+
|
41
|
+
def functions_for(property)
|
42
|
+
@functions && @functions.find_all { |f| f.calculate?(property) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def execute_filter(node)
|
46
|
+
if @filter.nil?
|
47
|
+
true
|
48
|
+
elsif @filter.arity != 1
|
49
|
+
node.wrapper.instance_eval(&@filter)
|
50
|
+
else
|
51
|
+
@filter.call(node)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def bulk_update?
|
56
|
+
@bulk_update
|
57
|
+
end
|
58
|
+
|
59
|
+
# ------------------------------------------------------------------------------------------------------------------
|
60
|
+
# Class Methods
|
61
|
+
# ------------------------------------------------------------------------------------------------------------------
|
62
|
+
|
63
|
+
@rule_nodes = {}
|
64
|
+
|
65
|
+
class << self
|
66
|
+
|
67
|
+
def add(clazz, rule_name, props, &block)
|
68
|
+
rule_node = rule_node_for(clazz.to_s)
|
69
|
+
rule_node.remove_rule(rule_name) # remove any previously inherited rules
|
70
|
+
rule = Rule.new(rule_name, props, &block)
|
71
|
+
rule_node.add_rule(rule)
|
72
|
+
rule
|
73
|
+
end
|
74
|
+
|
75
|
+
def rule_names_for(clazz)
|
76
|
+
rule_node = rule_node_for(clazz)
|
77
|
+
rule_node.rules.map { |rule| rule.rule_name }
|
78
|
+
end
|
79
|
+
|
80
|
+
def rule_node_for(clazz)
|
81
|
+
return nil if clazz.nil?
|
82
|
+
@rule_nodes[clazz.to_s] ||= RuleNode.new(clazz)
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_rule_node(node)
|
86
|
+
@rule_nodes && @rule_nodes.values.find { |rn| rn.rule_node?(node) }
|
87
|
+
end
|
88
|
+
|
89
|
+
def inherit(parent_class, subclass)
|
90
|
+
# copy all the rules
|
91
|
+
if rule_node = rule_node_for(parent_class)
|
92
|
+
rule_node.inherit(subclass)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def trigger?(node)
|
97
|
+
classname = node[:_classname]
|
98
|
+
@rule_nodes && classname && rule_node_for(classname) && !rule_node_for(classname).bulk_update?
|
99
|
+
end
|
100
|
+
|
101
|
+
def trigger_rules(node, *changes)
|
102
|
+
classname = node[:_classname]
|
103
|
+
return unless classname # there are no rules if there is not a :_classname property
|
104
|
+
rule_node = rule_node_for(classname)
|
105
|
+
rule_node.execute_rules(node, *changes)
|
106
|
+
|
107
|
+
# recursively add relationships for all the parent classes with rules that also pass for this node
|
108
|
+
recursive(node, rule_node.model_class, *changes)
|
109
|
+
end
|
110
|
+
|
111
|
+
def bulk_trigger_rules(classname, class_change, map)
|
112
|
+
rule_node = rule_node_for(classname)
|
113
|
+
rule_node.classes_changed(class_change)
|
114
|
+
if (clazz = rule_node.model_class.superclass) && clazz.include?(Neo4j::NodeMixin)
|
115
|
+
bulk_trigger_rules(clazz.name, class_change, map) if clazz.to_s != "Neo4j::Rails::Model"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def recursive(node, model_class, *changes)
|
122
|
+
if (clazz = model_class.superclass) && clazz.include?(Neo4j::NodeMixin)
|
123
|
+
if clazz.to_s != "Neo4j::Rails::Model"
|
124
|
+
rule_node = rule_node_for(clazz)
|
125
|
+
rule_node && rule_node.execute_rules(node, *changes)
|
126
|
+
recursive(node, clazz, *changes)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|