neo4j-wrapper 0.0.1-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|