neo4j 1.0.0.beta.9 → 1.0.0.beta.10
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/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
|