neo4j 1.0.0.beta.9 → 1.0.0.beta.10
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +5 -1971
- data/lib/neo4j/mapping/class_methods/relationship.rb +2 -2
- data/lib/neo4j/mapping/decl_relationship_dsl.rb +6 -1
- data/lib/neo4j/mapping/has_n.rb +17 -1
- data/lib/neo4j/node_traverser.rb +14 -1
- data/lib/neo4j/rails/model.rb +127 -31
- data/lib/neo4j/rails/tx_methods.rb +11 -0
- data/lib/neo4j/rails/value.rb +44 -1
- data/lib/neo4j/version.rb +1 -1
- data/lib/neo4j.rb +1 -0
- metadata +4 -59
- data/lib/neo4j.old/batch_inserter.rb +0 -144
- data/lib/neo4j.old/config.rb +0 -138
- data/lib/neo4j.old/event_handler.rb +0 -73
- data/lib/neo4j.old/extensions/activemodel.rb +0 -158
- data/lib/neo4j.old/extensions/aggregate/aggregate_enum.rb +0 -40
- data/lib/neo4j.old/extensions/aggregate/ext/node_mixin.rb +0 -69
- data/lib/neo4j.old/extensions/aggregate/node_aggregate.rb +0 -8
- data/lib/neo4j.old/extensions/aggregate/node_aggregate_mixin.rb +0 -331
- data/lib/neo4j.old/extensions/aggregate/node_aggregator.rb +0 -216
- data/lib/neo4j.old/extensions/aggregate/node_group.rb +0 -43
- data/lib/neo4j.old/extensions/aggregate/prop_group.rb +0 -30
- data/lib/neo4j.old/extensions/aggregate/property_enum.rb +0 -24
- data/lib/neo4j.old/extensions/aggregate/props_aggregate.rb +0 -8
- data/lib/neo4j.old/extensions/aggregate/props_aggregate_mixin.rb +0 -31
- data/lib/neo4j.old/extensions/aggregate/props_aggregator.rb +0 -80
- data/lib/neo4j.old/extensions/aggregate.rb +0 -12
- data/lib/neo4j.old/extensions/find_path.rb +0 -117
- data/lib/neo4j.old/extensions/graph_algo/all_simple_paths.rb +0 -133
- data/lib/neo4j.old/extensions/graph_algo/neo4j-graph-algo-0.3.jar +0 -0
- data/lib/neo4j.old/extensions/graph_algo.rb +0 -1
- data/lib/neo4j.old/extensions/reindexer.rb +0 -104
- data/lib/neo4j.old/extensions/rest/rest.rb +0 -336
- data/lib/neo4j.old/extensions/rest/rest_mixin.rb +0 -193
- data/lib/neo4j.old/extensions/rest/server.rb +0 -50
- data/lib/neo4j.old/extensions/rest/stubs.rb +0 -141
- data/lib/neo4j.old/extensions/rest.rb +0 -21
- data/lib/neo4j.old/extensions/rest_master.rb +0 -34
- data/lib/neo4j.old/extensions/rest_slave.rb +0 -31
- data/lib/neo4j.old/extensions/tx_tracker.rb +0 -392
- data/lib/neo4j.old/indexer.rb +0 -187
- data/lib/neo4j.old/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
- data/lib/neo4j.old/jars/neo4j-kernel-1.0.jar +0 -0
- data/lib/neo4j.old/jars.rb +0 -6
- data/lib/neo4j.old/mixins/java_list_mixin.rb +0 -139
- data/lib/neo4j.old/mixins/java_node_mixin.rb +0 -205
- data/lib/neo4j.old/mixins/java_property_mixin.rb +0 -169
- data/lib/neo4j.old/mixins/java_relationship_mixin.rb +0 -60
- data/lib/neo4j.old/mixins/migration_mixin.rb +0 -157
- data/lib/neo4j.old/mixins/node_mixin.rb +0 -249
- data/lib/neo4j.old/mixins/property_class_methods.rb +0 -265
- data/lib/neo4j.old/mixins/rel_class_methods.rb +0 -167
- data/lib/neo4j.old/mixins/relationship_mixin.rb +0 -103
- data/lib/neo4j.old/neo.rb +0 -247
- data/lib/neo4j.old/node.rb +0 -49
- data/lib/neo4j.old/reference_node.rb +0 -15
- data/lib/neo4j.old/relationship.rb +0 -85
- data/lib/neo4j.old/relationships/decl_relationship_dsl.rb +0 -164
- data/lib/neo4j.old/relationships/has_list.rb +0 -101
- data/lib/neo4j.old/relationships/has_n.rb +0 -129
- data/lib/neo4j.old/relationships/node_traverser.rb +0 -138
- data/lib/neo4j.old/relationships/relationship_dsl.rb +0 -149
- data/lib/neo4j.old/relationships/traversal_position.rb +0 -50
- data/lib/neo4j.old/relationships/wrappers.rb +0 -51
- data/lib/neo4j.old/search_result.rb +0 -72
- data/lib/neo4j.old/transaction.rb +0 -254
- data/lib/neo4j.old/version.rb +0 -3
@@ -1,331 +0,0 @@
|
|
1
|
-
module Neo4j::Aggregate
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
# Enables aggregation of nodes into groups.
|
6
|
-
# An aggregation is a node which in contains group nodes.
|
7
|
-
# A group node aggregates the properties of the nodes that belongs to its group.
|
8
|
-
#
|
9
|
-
# There are two ways of creating an aggregate.
|
10
|
-
# * Providing an enumeration of nodes.
|
11
|
-
# * Register a Node class. All nodes of this class will (can) be part of the aggregate.
|
12
|
-
#
|
13
|
-
# One example of usage of providing an enumeration of nodes is by taking the output from
|
14
|
-
# the Neo4j::NodeMixin#traverse as input to the aggregate method, or even
|
15
|
-
# create aggregates over aggregates.
|
16
|
-
#
|
17
|
-
# This mixin includes the Enumerable mixin.
|
18
|
-
#
|
19
|
-
# There is also a different aggregation which aggregates on properties
|
20
|
-
# instead of nodes - Neo4j::Aggregate::PropsAggregateMixin
|
21
|
-
#
|
22
|
-
# ==== Example - group by one property
|
23
|
-
#
|
24
|
-
# Let say we have nodes with properties :colour and we want to group them by colour:
|
25
|
-
#
|
26
|
-
# a = AggregateNode.new
|
27
|
-
#
|
28
|
-
# a.aggregate(nodes).group_by(:colour).execute
|
29
|
-
#
|
30
|
-
# The execute method is only needed when providing nodes (instead of a NodeClass) for the aggregate method.
|
31
|
-
# Print all three groups, one for each colour
|
32
|
-
#
|
33
|
-
# a.each{|n| puts n[:colour]}
|
34
|
-
#
|
35
|
-
# Print all nodes belonging to one colour group:
|
36
|
-
#
|
37
|
-
# a[:red].each {|node| puts node}
|
38
|
-
#
|
39
|
-
# ==== Example - Aggregating Properties
|
40
|
-
#
|
41
|
-
# The aggregator also aggregate properties. If a property does not exist on an aggregated group it will traverse all nodes in its group and
|
42
|
-
# return an enumeration of its values.
|
43
|
-
#
|
44
|
-
# Get an enumeration of names of people having favorite colour 'red'
|
45
|
-
#
|
46
|
-
# [*a[:red][:name]] => ['bertil', 'adam', 'adam']
|
47
|
-
#
|
48
|
-
# ==== Example - group by a property value which is transformed
|
49
|
-
#
|
50
|
-
# Let say way want to have group which include a range of values.
|
51
|
-
# Example - group by an age range, 0-4, 5-9, 10-14 etc...
|
52
|
-
#
|
53
|
-
# a = AggregateNode.new
|
54
|
-
# a.aggregate(an enumeration of nodes).group_by(:age).of_value{|age| age / 5}
|
55
|
-
#
|
56
|
-
# # traverse all people in age group 10-14 (3 maps to range 10-14)
|
57
|
-
# a[3].each {|x| ...}
|
58
|
-
#
|
59
|
-
# # traverse all groups
|
60
|
-
# a.each {|x| ...}
|
61
|
-
#
|
62
|
-
# # how many age groups are there ?
|
63
|
-
# a.aggregate_size
|
64
|
-
#
|
65
|
-
# # how many people are in age group 10-14
|
66
|
-
# a[3].aggregate_size
|
67
|
-
#
|
68
|
-
# ==== Example - Group by several properties
|
69
|
-
#
|
70
|
-
# The group_by method takes one or more property keys which it combines into one or more groups.
|
71
|
-
#
|
72
|
-
# node1 = Neo4j::Node.new; node1[:colour] = 'red'; node1[:type] = 'A'
|
73
|
-
# node2 = Neo4j::Node.new; node2[:colour] = 'red'; node2[:type] = 'B'
|
74
|
-
#
|
75
|
-
# agg_node = MyAggregateNode.new
|
76
|
-
# agg_node.aggregate([node1, node2]).group_by(:colour, :type)
|
77
|
-
#
|
78
|
-
# # node1 is member of two groups, red and A
|
79
|
-
# [*node1.aggregate_groups] # => [agg_node[:red], agg_node[:A]]
|
80
|
-
#
|
81
|
-
# # group A contains node1
|
82
|
-
# agg_node[:A].include?(node1) # => true
|
83
|
-
#
|
84
|
-
# # group red also contains node1
|
85
|
-
# agg_node[:red].include?(node1) # => true
|
86
|
-
#
|
87
|
-
# ==== Example - Appending new nodes to aggregates
|
88
|
-
#
|
89
|
-
# The aggregate node mixin implements the << operator that allows you to append nodes to the aggregate and the
|
90
|
-
# appended node will be put in the correct group.
|
91
|
-
#
|
92
|
-
# a = AggregateNode.new
|
93
|
-
# a.aggregate.group_by(:age).of_value{|age| age / 5}
|
94
|
-
#
|
95
|
-
# a << node1 << node2
|
96
|
-
#
|
97
|
-
# Notice that we do not need call the execute method. That method will be called each time we append nodes to the aggregate.
|
98
|
-
#
|
99
|
-
# ==== Example - trees of aggregates
|
100
|
-
#
|
101
|
-
# One example where this is needed is for having a tree structure of nodes with latitude and longitude grouped by a 'zoom' factor
|
102
|
-
#
|
103
|
-
# create an aggregation of groups where members have the same latitude longitude integer values (to_i)
|
104
|
-
# reg1 = agg_root.aggregate().group_by(:latitude, :longitude).map_value{|lat, lng| "#{(lat*1000).to_i}_#{(lng*1000).to_i}"}
|
105
|
-
#
|
106
|
-
# create another aggregation of groups where members have the same latitude longitude 1/10 value
|
107
|
-
# reg2 = agg_root.aggregate(reg1).group_by(:latitude, :longitude).map_value{|lat, lng| "#{(lat*100).to_i}_#{(lng*100).to_i" }
|
108
|
-
#
|
109
|
-
# Notice how the second aggregate uses the first aggregate (reg1). This will create the following structure with
|
110
|
-
# * node n1 - (latitude 42.1234 and longitude 12.1234) and
|
111
|
-
# * node n2 (latitude 42.1299 and longitude 12.1298)
|
112
|
-
# * node n3 (latitude 42.1333 and longitude 12.1298)
|
113
|
-
#
|
114
|
-
# Root agg_root
|
115
|
-
# | |
|
116
|
-
# Group 4212_1212 Group 4213_1212
|
117
|
-
# | |
|
118
|
-
# Group 42123_12123 Group 42133_12129
|
119
|
-
# | | |
|
120
|
-
# n1 n2 n3
|
121
|
-
#
|
122
|
-
# When the nodes n1,n2,n3 are added to the agg_root, e.g:
|
123
|
-
# agg_root << n1 << n2 << n3
|
124
|
-
#
|
125
|
-
#
|
126
|
-
# ==== Example - Add and remove nodes by events
|
127
|
-
#
|
128
|
-
# We want to both create and delete nodes and the aggregates should be updated automatically
|
129
|
-
# This is done by providing a NodeClass for the aggregate method.
|
130
|
-
# (it registering the aggregate dsl method as an event listener
|
131
|
-
#
|
132
|
-
# Here is an example that update the aggregate a on all nodes of type MyNode
|
133
|
-
# a = AggregateNode.new
|
134
|
-
#
|
135
|
-
# # the aggreate will get notified when nodes of type MyNode get changed
|
136
|
-
# a.aggregate(MyNode).group_by(:colour)
|
137
|
-
#
|
138
|
-
# Neo4j::Transaction.run { blue_node = MyNode.new; a.colour = 'blue' }
|
139
|
-
# # then the aggregate will be updated automatically since it listen to property change events
|
140
|
-
# a['blue'].size = 1
|
141
|
-
# [*a['blue']][0] # => blue_node
|
142
|
-
#
|
143
|
-
# blue_node[:colour] = 'red'
|
144
|
-
# a['blue'] # => nil
|
145
|
-
# [*a['red']] # => [blue_node]
|
146
|
-
# blue_node.delete
|
147
|
-
# a['red'] # => nil
|
148
|
-
#
|
149
|
-
#
|
150
|
-
# ===== TODO Only Aggregate ???
|
151
|
-
#
|
152
|
-
# a.aggregate([n1,n2])
|
153
|
-
# [*a] => [n1, n2]
|
154
|
-
#
|
155
|
-
# OR without aggregate method
|
156
|
-
# a << n1 << n2
|
157
|
-
#
|
158
|
-
# ===== Example Group by Each (1)
|
159
|
-
#
|
160
|
-
# class MyRoot
|
161
|
-
# include AggregateEach
|
162
|
-
# end
|
163
|
-
#
|
164
|
-
# a = MyRoot.new
|
165
|
-
#
|
166
|
-
# n1 = [jan=>1, feb=>5, mars=>2, apr=>10, ...]
|
167
|
-
# n2 = [jan=>2, feb=>0, mars=>2, apr=>10, ...]
|
168
|
-
#
|
169
|
-
# a.aggregate_each([n1,n2]).group(:jan,:feb,:mars).by(:q1)
|
170
|
-
# [*a[:q1]] = [g1,g2]
|
171
|
-
# g1.props => [n1.neo_id, jan=>1, feb=>5, ..., dec=>]
|
172
|
-
# [*g1] => [1,5,2]
|
173
|
-
#
|
174
|
-
# ===== Example Group by Each (2)
|
175
|
-
#
|
176
|
-
# n1 = [:colour => 'red', :age => 10]
|
177
|
-
# n2 = [:colour => 'blue', :age => 11]
|
178
|
-
# n3 = [:colour => 'red', :age => 12]
|
179
|
-
#
|
180
|
-
# a.aggregate_each([n1,n2,n3]).group_by(:colour, :age)
|
181
|
-
# n1.aggregate_groups = [g1]
|
182
|
-
# [*g1] = ['red', 10]
|
183
|
-
# [*a] => [g1,g2,g3]
|
184
|
-
#
|
185
|
-
# [*g2] = ['blue', 11]
|
186
|
-
# g1.props => [
|
187
|
-
#
|
188
|
-
#
|
189
|
-
# ===== Example Group by new property
|
190
|
-
#
|
191
|
-
# q1.aggregate_each(nodes).group_by(:jan,:feb,:mars)
|
192
|
-
# q2.aggregate_each(nodes).group_by(:apr,:may,:june)
|
193
|
-
#
|
194
|
-
#
|
195
|
-
# T O D O - G R O U P _ B Y the only needed one (not group and by)
|
196
|
-
# n1 = [jan=>1, feb=>5, mars=>2, apr=>10, ...]
|
197
|
-
# n2 = [jan=>2, feb=>0, mars=>2, apr=>10, ...]
|
198
|
-
#
|
199
|
-
# [*q1] => [g1,g2]
|
200
|
-
# [*g1] => [1,5,2]
|
201
|
-
# [*g2] => [2,0,2]
|
202
|
-
#
|
203
|
-
# [*q2] => [g3,g4]
|
204
|
-
# n1.aggregate_groups = [g1,g2]
|
205
|
-
# n1[:q1] => nil OR [1,5,2] ????
|
206
|
-
# a[:q1] => [1,5,2,2,0,2]
|
207
|
-
# n1.each {|n| n[:q1]}
|
208
|
-
#
|
209
|
-
# SUM
|
210
|
-
# a.aggregate([n1,n2]).group(:jan,:feb,:mars).by(:q1).sum
|
211
|
-
# n1[:q1] => 8
|
212
|
-
# a[:q1] => 12
|
213
|
-
#
|
214
|
-
#
|
215
|
-
# m1 = [revenue => 1000]
|
216
|
-
# m2 = [revenue => 500]
|
217
|
-
# m3 = [revenue => 2000]
|
218
|
-
# a2.aggregate([m1,m2,m3]).group(:revenue).by(:rev).map_value{|v| v >= 1000 ? "good" : "bad"}.count
|
219
|
-
#
|
220
|
-
# a[:rev] => ["good", "good", "bad"]
|
221
|
-
# a[:rev]["good"] => 2
|
222
|
-
# a[:rev]["bad"] => 1
|
223
|
-
module NodeAggregateMixin
|
224
|
-
include Neo4j::NodeMixin
|
225
|
-
include Enumerable
|
226
|
-
|
227
|
-
|
228
|
-
# The number of groups that this aggregate contains
|
229
|
-
def aggregate_size
|
230
|
-
_java_node.set_property("aggregate_size", 0) unless _java_node.has_property("aggregate_size")
|
231
|
-
self[:aggregate_size]
|
232
|
-
end
|
233
|
-
|
234
|
-
|
235
|
-
# Internal method - set the number of groups that this node contains
|
236
|
-
# We can then use this property instead of traversing and counting each node in order to find out how many groups there are.
|
237
|
-
def aggregate_size=(value) # :nodoc:
|
238
|
-
self[:aggregate_size] = value
|
239
|
-
end
|
240
|
-
|
241
|
-
# Creates aggregated nodes by grouping nodes by one or more property values.
|
242
|
-
# Raises an exception if the aggregation already exists.
|
243
|
-
#
|
244
|
-
# ==== Parameters
|
245
|
-
# * aggregate(optional an enumeration) - specifies which nodes it should aggregate into groups of nodes
|
246
|
-
#
|
247
|
-
# If the no argument is given for the aggregate method then nodes can be appended to the aggregate using the << method.
|
248
|
-
#
|
249
|
-
# ==== Returns
|
250
|
-
# an object that has the following methods
|
251
|
-
# * group_by(*keys) - specifies which property or properties values it should group by
|
252
|
-
# * group_each_by - same as group_by but instead of combinding the properties it creates new groups for each given property
|
253
|
-
# * execute - executes the aggregation, creates new nodes that groups the specified nodes
|
254
|
-
#
|
255
|
-
# :api: public
|
256
|
-
def aggregate(nodes_or_filter=nil)
|
257
|
-
# setting a property here using neo4j.rb might trigger events which we do not want
|
258
|
-
@aggregator = NodeAggregator.new(self, nodes_or_filter)
|
259
|
-
end
|
260
|
-
|
261
|
-
# Appends one or a whole enumeration of nodes to the existing aggregation.
|
262
|
-
# Each node will be put into aggregate groups that was specified using the aggregate method.
|
263
|
-
#
|
264
|
-
# If the node does not have a property(ies) used for grouping nodes then the node will node be appendend to the aggreation.
|
265
|
-
# Example:
|
266
|
-
# my_agg.aggregate.group_by(:colour)
|
267
|
-
# my_agg << Neo4j::Node.new # this node will not be added since it is missing the colour property
|
268
|
-
#
|
269
|
-
# ==== Parameter
|
270
|
-
# * node(an enumeration, or one node) - specifies which node(s) should be appneit should aggregate into groups of nodes
|
271
|
-
#
|
272
|
-
# ==== Returns
|
273
|
-
# self
|
274
|
-
#
|
275
|
-
def <<(node)
|
276
|
-
# @aggregator.execute if @aggregator
|
277
|
-
if node.kind_of?(Enumerable)
|
278
|
-
@aggregator.execute(node)
|
279
|
-
else
|
280
|
-
@aggregator.execute([node])
|
281
|
-
end
|
282
|
-
self
|
283
|
-
end
|
284
|
-
|
285
|
-
|
286
|
-
# Checks if the given node is include in this aggregate
|
287
|
-
#
|
288
|
-
# ==== Returns
|
289
|
-
# true if the aggregate includes the given node.
|
290
|
-
#
|
291
|
-
# :api: public
|
292
|
-
def include_node?(node)
|
293
|
-
key = @aggregator.group_key_of(node)
|
294
|
-
group = group_node(key)
|
295
|
-
return false if group.nil?
|
296
|
-
group.include?(node)
|
297
|
-
end
|
298
|
-
|
299
|
-
# Returns the group with the given key
|
300
|
-
# If there is no group with that key it returns nil
|
301
|
-
#
|
302
|
-
# :api: public
|
303
|
-
def group_node(key)
|
304
|
-
@aggregator.execute if @aggregator
|
305
|
-
rels.outgoing(key).nodes.find{|n| n.kind_of? NodeGroup}
|
306
|
-
end
|
307
|
-
|
308
|
-
|
309
|
-
# Overrides the [] method
|
310
|
-
#
|
311
|
-
# If there is a relationship of the given key, and that node is kind_of?
|
312
|
-
# that that relationships point to will be returned (as an Enumeration).
|
313
|
-
# Otherwise, return the property of this node.
|
314
|
-
#
|
315
|
-
def [](key)
|
316
|
-
node = group_node(key)
|
317
|
-
return node unless node.nil?
|
318
|
-
super(key)
|
319
|
-
end
|
320
|
-
|
321
|
-
|
322
|
-
def each
|
323
|
-
@aggregator.execute if @aggregator
|
324
|
-
rels.outgoing.nodes.each {|n| yield n if n.kind_of? NodeGroup}
|
325
|
-
end
|
326
|
-
|
327
|
-
end
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
end
|
@@ -1,216 +0,0 @@
|
|
1
|
-
module Neo4j::Aggregate
|
2
|
-
# Used to create a DSL describing how to aggregate an enumeration of nodes
|
3
|
-
#
|
4
|
-
# :api: public
|
5
|
-
class NodeAggregator
|
6
|
-
attr_accessor :root_dsl
|
7
|
-
|
8
|
-
def initialize(root_node, dsl_nodes_or_filter)
|
9
|
-
@root_node = root_node
|
10
|
-
self.root_dsl = self #if not chained dsl then the root dsl is self
|
11
|
-
|
12
|
-
if dsl_nodes_or_filter.kind_of?(self.class)
|
13
|
-
# we are chaining aggregates
|
14
|
-
@child_dsl = dsl_nodes_or_filter
|
15
|
-
@child_dsl.root_dsl = self # the child has a pointer to the parent
|
16
|
-
elsif dsl_nodes_or_filter.kind_of?(Enumerable)
|
17
|
-
# we are aggregating an enumerable set of nodes
|
18
|
-
@nodes = dsl_nodes_or_filter
|
19
|
-
elsif (dsl_nodes_or_filter.kind_of?(Class) and dsl_nodes_or_filter.ancestors.include?(Neo4j::NodeMixin))
|
20
|
-
# We are listening for events on Neo4j nodes - that will be included in the aggregates
|
21
|
-
@filter = dsl_nodes_or_filter
|
22
|
-
# Register with the Neo4j event handler
|
23
|
-
Neo4j.event_handler.add(self)
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
|
29
|
-
# Unregisters this aggregate so that it will not be notified any longer
|
30
|
-
# on Neo4j node events. Used when we create an aggregate that is registered
|
31
|
-
# with the Neo4j even listener by including a filter in the aggregate method
|
32
|
-
#
|
33
|
-
# ==== Example
|
34
|
-
# agg_reg = my_aggregate.aggregate(MyNode).group_by(:something)
|
35
|
-
# # add some MyNodes that my_aggregate will aggregate into groups
|
36
|
-
# MyNode.new # etc...
|
37
|
-
# # we now do not want to add more nodes using the aggregate above - unregister it
|
38
|
-
# agg_reg.unregister
|
39
|
-
# # no more nodes will be appended /deleted /modified in the my_aggregate.
|
40
|
-
#
|
41
|
-
def unregister
|
42
|
-
Neo4j.event_handler.remove(self)
|
43
|
-
end
|
44
|
-
|
45
|
-
def to_s
|
46
|
-
"Aggregator group_by #{@group_by} filter #{!@filter.nil?} object_id: #{self.object_id} child: #{!@child_dsl.nil?}"
|
47
|
-
end
|
48
|
-
|
49
|
-
|
50
|
-
# called from neo4j event handler
|
51
|
-
# :api: private
|
52
|
-
def on_property_changed(node, prop_key, old_value, new_value) # :nodoc:
|
53
|
-
return if node.class != @filter
|
54
|
-
return unless @group_by.include?(prop_key.to_sym)
|
55
|
-
old_node = node.props
|
56
|
-
old_node[prop_key] = old_value
|
57
|
-
root_dsl.on_prop_added(node, node, old_node)
|
58
|
-
on_prop_deleted(node, node, old_node)
|
59
|
-
end
|
60
|
-
|
61
|
-
# called from neo4j event handler
|
62
|
-
# :api: private
|
63
|
-
def on_node_deleted(node) # :nodoc:
|
64
|
-
return if node.class != @filter
|
65
|
-
# node.print(:incoming, 2)
|
66
|
-
node.rels.incoming(:aggregate).filter{start_node.property? :aggregate_size}.each do |group_rel|
|
67
|
-
group_node = group_rel.start_node
|
68
|
-
group_node.aggregate_size -= 1
|
69
|
-
# should we delete the whole group ?
|
70
|
-
delete_group(group_node) if (group_node.aggregate_size == 0)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def delete_group(group_node) # :nodoc:
|
75
|
-
# get parent aggregates and decrease the aggregate size
|
76
|
-
group_node.rels.incoming.nodes.each do |parent_group|
|
77
|
-
next unless parent_group.respond_to? :aggregate_size
|
78
|
-
parent_group[:aggregate_size] -= 1
|
79
|
-
delete_group(parent_group) if parent_group[:aggregate_size] == 0
|
80
|
-
end
|
81
|
-
group_node.del
|
82
|
-
end
|
83
|
-
|
84
|
-
|
85
|
-
def on_prop_deleted(node, curr_node_values, old_node_values) # :nodoc:
|
86
|
-
old_group_keys = group_key_of(old_node_values)
|
87
|
-
new_group_keys = group_key_of(curr_node_values)
|
88
|
-
|
89
|
-
# keys that are removed
|
90
|
-
removed = old_group_keys - new_group_keys
|
91
|
-
|
92
|
-
removed.each do |key|
|
93
|
-
member_of = [*node.rels.incoming(:aggregate).filter{self[:aggregate_group] == key}]
|
94
|
-
raise "same group key used in several aggregate groups, strange #{member_of.size}" if member_of.size > 1
|
95
|
-
next if member_of.empty?
|
96
|
-
group_node = member_of[0].start_node
|
97
|
-
group_node.aggregate_size -= 1
|
98
|
-
member_of[0].delete
|
99
|
-
|
100
|
-
# should we delete the whole group
|
101
|
-
delete_group(group_node) if (group_node.aggregate_size == 0)
|
102
|
-
end
|
103
|
-
|
104
|
-
end
|
105
|
-
|
106
|
-
def on_prop_added(node, curr_node_values, old_node_values) # :nodoc:
|
107
|
-
old_group_keys = group_key_of(old_node_values)
|
108
|
-
new_group_keys = group_key_of(curr_node_values)
|
109
|
-
|
110
|
-
# keys that are added
|
111
|
-
added = new_group_keys - old_group_keys
|
112
|
-
added.each { |key| root_dsl.create_group_for_key(@root_node, node, key) }
|
113
|
-
end
|
114
|
-
|
115
|
-
|
116
|
-
# Specifies which properties we should group on.
|
117
|
-
# All thos properties can be combined to create a new group.
|
118
|
-
#
|
119
|
-
# :api: public
|
120
|
-
def group_by(*keys)
|
121
|
-
@group_by = keys
|
122
|
-
self
|
123
|
-
end
|
124
|
-
|
125
|
-
|
126
|
-
# Maps the values of the given properties (in group_by or group_by_each).
|
127
|
-
# If this method is not used the group name will be the same as the property value.
|
128
|
-
#
|
129
|
-
# :api: public
|
130
|
-
def map_value(&map_func)
|
131
|
-
@map_func = map_func
|
132
|
-
self
|
133
|
-
end
|
134
|
-
|
135
|
-
# Create a group key for given node
|
136
|
-
# :api: private
|
137
|
-
def group_key_of(node)
|
138
|
-
values = @group_by.map{|key| node[key.to_s]}
|
139
|
-
|
140
|
-
# are there any keys ?
|
141
|
-
return [] if values.to_s.empty?
|
142
|
-
|
143
|
-
# should we map the values ?
|
144
|
-
if !@map_func.nil?
|
145
|
-
raise "Wrong number of argument of map_value function, expected #{values.size} args but it takes #{@map_func.arity} args" if @map_func.arity != values.size
|
146
|
-
values = @map_func.call(*values)
|
147
|
-
values = [values] unless values.kind_of? Enumerable
|
148
|
-
end
|
149
|
-
|
150
|
-
|
151
|
-
# check all values and expand enumerable values
|
152
|
-
group_keys = [*values.inject(Set.new) do |result, value|
|
153
|
-
if value.respond_to?(:to_a)
|
154
|
-
result.merge([*value])
|
155
|
-
else
|
156
|
-
result << value
|
157
|
-
end
|
158
|
-
end]
|
159
|
-
|
160
|
-
# if we are not grouping by_each then there will only be one group_key - join it
|
161
|
-
group_keys = [group_keys] unless group_keys.respond_to?(:each)
|
162
|
-
group_keys
|
163
|
-
end
|
164
|
-
|
165
|
-
# Executes the DSL and creates the specified groups.
|
166
|
-
# This method is not necessary to call, since it will automatically be called when needed.
|
167
|
-
#
|
168
|
-
# :api: public
|
169
|
-
def execute(nodes = @nodes)
|
170
|
-
return if nodes.nil?
|
171
|
-
|
172
|
-
# prevent execute to execute again with the same nodes
|
173
|
-
@nodes = nil
|
174
|
-
|
175
|
-
nodes.each { |node| root_dsl.create_groups(@root_node, node) }
|
176
|
-
end
|
177
|
-
|
178
|
-
# :api: private
|
179
|
-
def create_groups(parent, node)
|
180
|
-
group_key_of(node).each { |key| create_group_for_key(parent, node, key) }
|
181
|
-
end
|
182
|
-
|
183
|
-
# :api: private
|
184
|
-
def create_group_for_key(parent, node, key)
|
185
|
-
# find a group node for the given key
|
186
|
-
group_node = parent.rels.outgoing(key).nodes.find{|n| n.kind_of? NodeGroup}
|
187
|
-
|
188
|
-
# if no group key is found create a new one
|
189
|
-
group_node ||= create_group_node(parent, key)
|
190
|
-
|
191
|
-
# check if it is the leaf node or not
|
192
|
-
if (@child_dsl)
|
193
|
-
# this is not the leaf aggregate dsl, let the child node add the node instead
|
194
|
-
@child_dsl.create_groups(group_node, node)
|
195
|
-
else
|
196
|
-
# this IS a leaf aggregate dsl, add node to the group
|
197
|
-
rel_type = node.kind_of?(NodeGroup)? key : :aggregate
|
198
|
-
rel = group_node.add_rel(rel_type, node)
|
199
|
-
rel[:aggregate_group] = key
|
200
|
-
# increase the size counter on this group
|
201
|
-
group_node.aggregate_size += 1
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
# :api: private
|
206
|
-
def create_group_node(parent, key)
|
207
|
-
new_node = NodeGroup.create(key)
|
208
|
-
rel = parent.add_rel(key, new_node)
|
209
|
-
parent.aggregate_size += 1 # another group was created
|
210
|
-
rel[:aggregate_group] = key
|
211
|
-
new_node
|
212
|
-
end
|
213
|
-
|
214
|
-
end
|
215
|
-
|
216
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
module Neo4j::Aggregate
|
2
|
-
|
3
|
-
# This is the group node. When a new aggregate group is created it will be of this type.
|
4
|
-
# Includes the Enumerable mixin in order to iterator over each node member in the group.
|
5
|
-
# Overrides [] and []= properties, so that we can access aggregated properties or relationships.
|
6
|
-
#
|
7
|
-
# :api: private
|
8
|
-
class NodeGroup #:nodoc:
|
9
|
-
include Neo4j::NodeMixin
|
10
|
-
include Enumerable
|
11
|
-
|
12
|
-
property :aggregate_group, :aggregate_size
|
13
|
-
|
14
|
-
def self.create(aggregate_group)
|
15
|
-
new_node = NodeGroup.new
|
16
|
-
new_node.aggregate_group = aggregate_group.kind_of?(Symbol)? aggregate_group.to_s : aggregate_group
|
17
|
-
new_node.aggregate_size = 0
|
18
|
-
new_node
|
19
|
-
end
|
20
|
-
|
21
|
-
def each
|
22
|
-
rels.outgoing.nodes.each { |n| yield n }
|
23
|
-
end
|
24
|
-
|
25
|
-
# :api: private
|
26
|
-
def [](key)
|
27
|
-
value = super(key)
|
28
|
-
return value unless value.nil?
|
29
|
-
|
30
|
-
sub_group = rels.outgoing(key).nodes.first
|
31
|
-
return sub_group unless sub_group.nil?
|
32
|
-
|
33
|
-
# traverse all sub nodes and get their properties
|
34
|
-
PropertyEnum.new(rels.outgoing.nodes, key)
|
35
|
-
end
|
36
|
-
|
37
|
-
# def []=(key, value)
|
38
|
-
# super key, value
|
39
|
-
# self.get_property(key)
|
40
|
-
# end
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
module Neo4j::Aggregate
|
2
|
-
|
3
|
-
class PropGroup
|
4
|
-
include Neo4j::NodeMixin
|
5
|
-
include Enumerable
|
6
|
-
|
7
|
-
has_one :aggregate, :cascade_delete => :incoming
|
8
|
-
property :aggregate_group, :aggregate_size, :group_by
|
9
|
-
|
10
|
-
def each
|
11
|
-
group_by.split(',').each do |group|
|
12
|
-
yield aggregate[group]
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
# :api: private
|
17
|
-
def get_property(key)
|
18
|
-
value = super(key)
|
19
|
-
return value unless value.nil?
|
20
|
-
return nil unless aggregate
|
21
|
-
aggregate[key]
|
22
|
-
end
|
23
|
-
|
24
|
-
def ignore_incoming_cascade_delete? (relationship)
|
25
|
-
super || relationship.start_node.kind_of?(Neo4j::Aggregate::PropsAggregate)
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
# Used to aggregate property values.
|
2
|
-
#
|
3
|
-
# :api: private
|
4
|
-
class Neo4j::Aggregate::PropertyEnum #:nodoc:
|
5
|
-
include Enumerable
|
6
|
-
|
7
|
-
def initialize(nodes, property)
|
8
|
-
@nodes = nodes
|
9
|
-
@property = property
|
10
|
-
end
|
11
|
-
|
12
|
-
def each
|
13
|
-
@nodes.each do |n|
|
14
|
-
v = n[@property]
|
15
|
-
if v.kind_of?(Enumerable)
|
16
|
-
v.each {|vv| yield vv}
|
17
|
-
else
|
18
|
-
yield v
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
module Neo4j::Aggregate
|
2
|
-
|
3
|
-
# Rules properties on one or more nodes.
|
4
|
-
# Can also be used to apply functions (e.g. sum/average) on a set of properties.
|
5
|
-
#
|
6
|
-
module PropsAggregateMixin
|
7
|
-
include Neo4j::NodeMixin
|
8
|
-
include Enumerable
|
9
|
-
|
10
|
-
has_list :groups, :counter => true #, :cascade_delete => :incoming
|
11
|
-
|
12
|
-
def init_node(*args)
|
13
|
-
@aggregate_id = args[0] unless args.empty?
|
14
|
-
end
|
15
|
-
|
16
|
-
def aggregate_size
|
17
|
-
@aggregator.execute if @aggregator
|
18
|
-
groups.size
|
19
|
-
end
|
20
|
-
|
21
|
-
def each
|
22
|
-
@aggregator.execute if @aggregator
|
23
|
-
groups.each {|sub_group| sub_group.each {|val| yield val}}
|
24
|
-
end
|
25
|
-
|
26
|
-
def aggregate(agg_id)
|
27
|
-
@aggregator = PropsAggregator.new(self, agg_id.to_s)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|