activegraph 11.0.0.beta.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.
Files changed (144) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2016 -0
  3. data/CONTRIBUTORS +12 -0
  4. data/Gemfile +24 -0
  5. data/README.md +111 -0
  6. data/activegraph.gemspec +52 -0
  7. data/bin/rake +17 -0
  8. data/config/locales/en.yml +5 -0
  9. data/config/neo4j/add_classnames.yml +1 -0
  10. data/config/neo4j/config.yml +35 -0
  11. data/lib/active_graph.rb +123 -0
  12. data/lib/active_graph/ansi.rb +14 -0
  13. data/lib/active_graph/attribute_set.rb +32 -0
  14. data/lib/active_graph/base.rb +77 -0
  15. data/lib/active_graph/class_arguments.rb +39 -0
  16. data/lib/active_graph/config.rb +135 -0
  17. data/lib/active_graph/core.rb +14 -0
  18. data/lib/active_graph/core/connection_failed_error.rb +6 -0
  19. data/lib/active_graph/core/cypher_error.rb +37 -0
  20. data/lib/active_graph/core/entity.rb +11 -0
  21. data/lib/active_graph/core/instrumentable.rb +37 -0
  22. data/lib/active_graph/core/label.rb +135 -0
  23. data/lib/active_graph/core/logging.rb +44 -0
  24. data/lib/active_graph/core/node.rb +15 -0
  25. data/lib/active_graph/core/querable.rb +41 -0
  26. data/lib/active_graph/core/query.rb +485 -0
  27. data/lib/active_graph/core/query_builder.rb +18 -0
  28. data/lib/active_graph/core/query_clauses.rb +727 -0
  29. data/lib/active_graph/core/query_ext.rb +24 -0
  30. data/lib/active_graph/core/query_find_in_batches.rb +46 -0
  31. data/lib/active_graph/core/record.rb +51 -0
  32. data/lib/active_graph/core/result.rb +31 -0
  33. data/lib/active_graph/core/schema.rb +65 -0
  34. data/lib/active_graph/core/schema_errors.rb +12 -0
  35. data/lib/active_graph/core/wrappable.rb +30 -0
  36. data/lib/active_graph/errors.rb +59 -0
  37. data/lib/active_graph/lazy_attribute_hash.rb +38 -0
  38. data/lib/active_graph/migration.rb +148 -0
  39. data/lib/active_graph/migrations.rb +27 -0
  40. data/lib/active_graph/migrations/base.rb +77 -0
  41. data/lib/active_graph/migrations/check_pending.rb +20 -0
  42. data/lib/active_graph/migrations/helpers.rb +105 -0
  43. data/lib/active_graph/migrations/helpers/id_property.rb +72 -0
  44. data/lib/active_graph/migrations/helpers/relationships.rb +66 -0
  45. data/lib/active_graph/migrations/helpers/schema.rb +63 -0
  46. data/lib/active_graph/migrations/migration_file.rb +24 -0
  47. data/lib/active_graph/migrations/runner.rb +195 -0
  48. data/lib/active_graph/migrations/schema.rb +64 -0
  49. data/lib/active_graph/migrations/schema_migration.rb +14 -0
  50. data/lib/active_graph/model_schema.rb +139 -0
  51. data/lib/active_graph/node.rb +110 -0
  52. data/lib/active_graph/node/callbacks.rb +8 -0
  53. data/lib/active_graph/node/dependent.rb +11 -0
  54. data/lib/active_graph/node/dependent/association_methods.rb +49 -0
  55. data/lib/active_graph/node/dependent/query_proxy_methods.rb +52 -0
  56. data/lib/active_graph/node/dependent_callbacks.rb +31 -0
  57. data/lib/active_graph/node/enum.rb +26 -0
  58. data/lib/active_graph/node/has_n.rb +602 -0
  59. data/lib/active_graph/node/has_n/association.rb +278 -0
  60. data/lib/active_graph/node/has_n/association/rel_factory.rb +61 -0
  61. data/lib/active_graph/node/has_n/association/rel_wrapper.rb +23 -0
  62. data/lib/active_graph/node/has_n/association_cypher_methods.rb +108 -0
  63. data/lib/active_graph/node/id_property.rb +224 -0
  64. data/lib/active_graph/node/id_property/accessor.rb +62 -0
  65. data/lib/active_graph/node/initialize.rb +21 -0
  66. data/lib/active_graph/node/labels.rb +207 -0
  67. data/lib/active_graph/node/labels/index.rb +37 -0
  68. data/lib/active_graph/node/labels/reloading.rb +21 -0
  69. data/lib/active_graph/node/node_list_formatter.rb +13 -0
  70. data/lib/active_graph/node/node_wrapper.rb +54 -0
  71. data/lib/active_graph/node/orm_adapter.rb +82 -0
  72. data/lib/active_graph/node/persistence.rb +186 -0
  73. data/lib/active_graph/node/property.rb +60 -0
  74. data/lib/active_graph/node/query.rb +76 -0
  75. data/lib/active_graph/node/query/query_proxy.rb +367 -0
  76. data/lib/active_graph/node/query/query_proxy_eager_loading.rb +177 -0
  77. data/lib/active_graph/node/query/query_proxy_eager_loading/association_tree.rb +75 -0
  78. data/lib/active_graph/node/query/query_proxy_enumerable.rb +110 -0
  79. data/lib/active_graph/node/query/query_proxy_find_in_batches.rb +19 -0
  80. data/lib/active_graph/node/query/query_proxy_link.rb +139 -0
  81. data/lib/active_graph/node/query/query_proxy_methods.rb +303 -0
  82. data/lib/active_graph/node/query/query_proxy_methods_of_mass_updating.rb +99 -0
  83. data/lib/active_graph/node/query_methods.rb +68 -0
  84. data/lib/active_graph/node/reflection.rb +86 -0
  85. data/lib/active_graph/node/rels.rb +11 -0
  86. data/lib/active_graph/node/scope.rb +166 -0
  87. data/lib/active_graph/node/unpersisted.rb +48 -0
  88. data/lib/active_graph/node/validations.rb +59 -0
  89. data/lib/active_graph/paginated.rb +27 -0
  90. data/lib/active_graph/railtie.rb +108 -0
  91. data/lib/active_graph/relationship.rb +68 -0
  92. data/lib/active_graph/relationship/callbacks.rb +21 -0
  93. data/lib/active_graph/relationship/initialize.rb +28 -0
  94. data/lib/active_graph/relationship/persistence.rb +133 -0
  95. data/lib/active_graph/relationship/persistence/query_factory.rb +95 -0
  96. data/lib/active_graph/relationship/property.rb +92 -0
  97. data/lib/active_graph/relationship/query.rb +99 -0
  98. data/lib/active_graph/relationship/rel_wrapper.rb +31 -0
  99. data/lib/active_graph/relationship/related_node.rb +87 -0
  100. data/lib/active_graph/relationship/types.rb +80 -0
  101. data/lib/active_graph/relationship/validations.rb +8 -0
  102. data/lib/active_graph/schema/operation.rb +102 -0
  103. data/lib/active_graph/shared.rb +48 -0
  104. data/lib/active_graph/shared/attributes.rb +217 -0
  105. data/lib/active_graph/shared/callbacks.rb +66 -0
  106. data/lib/active_graph/shared/cypher.rb +37 -0
  107. data/lib/active_graph/shared/declared_properties.rb +204 -0
  108. data/lib/active_graph/shared/declared_property.rb +109 -0
  109. data/lib/active_graph/shared/declared_property/index.rb +37 -0
  110. data/lib/active_graph/shared/enum.rb +167 -0
  111. data/lib/active_graph/shared/filtered_hash.rb +79 -0
  112. data/lib/active_graph/shared/identity.rb +34 -0
  113. data/lib/active_graph/shared/initialize.rb +65 -0
  114. data/lib/active_graph/shared/marshal.rb +23 -0
  115. data/lib/active_graph/shared/mass_assignment.rb +63 -0
  116. data/lib/active_graph/shared/permitted_attributes.rb +28 -0
  117. data/lib/active_graph/shared/persistence.rb +272 -0
  118. data/lib/active_graph/shared/property.rb +249 -0
  119. data/lib/active_graph/shared/query_factory.rb +122 -0
  120. data/lib/active_graph/shared/rel_type_converters.rb +43 -0
  121. data/lib/active_graph/shared/serialized_properties.rb +30 -0
  122. data/lib/active_graph/shared/type_converters.rb +439 -0
  123. data/lib/active_graph/shared/typecasted_attributes.rb +99 -0
  124. data/lib/active_graph/shared/typecaster.rb +53 -0
  125. data/lib/active_graph/shared/validations.rb +44 -0
  126. data/lib/active_graph/tasks/migration.rake +204 -0
  127. data/lib/active_graph/timestamps.rb +11 -0
  128. data/lib/active_graph/timestamps/created.rb +9 -0
  129. data/lib/active_graph/timestamps/updated.rb +9 -0
  130. data/lib/active_graph/transaction.rb +22 -0
  131. data/lib/active_graph/transactions.rb +57 -0
  132. data/lib/active_graph/type_converters.rb +7 -0
  133. data/lib/active_graph/undeclared_properties.rb +53 -0
  134. data/lib/active_graph/version.rb +3 -0
  135. data/lib/active_graph/wrapper.rb +4 -0
  136. data/lib/rails/generators/active_graph/migration/migration_generator.rb +16 -0
  137. data/lib/rails/generators/active_graph/migration/templates/migration.erb +9 -0
  138. data/lib/rails/generators/active_graph/model/model_generator.rb +89 -0
  139. data/lib/rails/generators/active_graph/model/templates/migration.erb +11 -0
  140. data/lib/rails/generators/active_graph/model/templates/model.erb +15 -0
  141. data/lib/rails/generators/active_graph/upgrade_v8/templates/migration.erb +17 -0
  142. data/lib/rails/generators/active_graph/upgrade_v8/upgrade_v8_generator.rb +34 -0
  143. data/lib/rails/generators/active_graph_generator.rb +121 -0
  144. metadata +423 -0
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveGraph
4
+ module Core
5
+ module Node
6
+ def neo_id
7
+ id
8
+ end
9
+
10
+ def labels
11
+ @labels ||= super
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,41 @@
1
+ require 'active_graph/core/instrumentable'
2
+ require 'active_graph/transaction'
3
+ require 'active_graph/core/query_builder'
4
+ require 'active_graph/core/record'
5
+
6
+ module ActiveGraph
7
+ module Core
8
+ module Querable
9
+ extend ActiveSupport::Concern
10
+ include Instrumentable
11
+
12
+ class_methods do
13
+ def query(*args)
14
+ options = case args.size
15
+ when 3
16
+ args.pop
17
+ when 2
18
+ args.pop if args[0].is_a?(::ActiveGraph::Core::Query)
19
+ end || {}
20
+
21
+ query_run(QueryBuilder.query(*args), options)
22
+ end
23
+
24
+ def setup_query!(query, options = {})
25
+ return if options[:skip_instrumentation]
26
+ ActiveSupport::Notifications.instrument('neo4j.core.cypher_query', query: query)
27
+ end
28
+
29
+ def query_run(query, options = {})
30
+ setup_query!(query, skip_instrumentation: options[:skip_instrumentation])
31
+
32
+ ActiveSupport::Notifications.instrument('neo4j.core.bolt.request') do
33
+ transaction do |tx|
34
+ tx.run(query.cypher, query.parameters).tap { |result| result.wrap = options[:wrap] != false }
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,485 @@
1
+ require 'active_graph/core/query_clauses'
2
+ require 'active_graph/core/query_find_in_batches'
3
+ require 'active_support/notifications'
4
+
5
+ module ActiveGraph
6
+ module Core
7
+ # Allows for generation of cypher queries via ruby method calls (inspired by ActiveRecord / arel syntax)
8
+ #
9
+ # Can be used to express cypher queries in ruby nicely, or to more easily generate queries programatically.
10
+ #
11
+ # Also, queries can be passed around an application to progressively build a query across different concerns
12
+ #
13
+ # See also the following link for full cypher language documentation:
14
+ # http://docs.neo4j.org/chunked/milestone/cypher-query-lang.html
15
+ class Query
16
+ include ActiveGraph::Core::QueryClauses
17
+ include ActiveGraph::Core::QueryFindInBatches
18
+ DEFINED_CLAUSES = {}
19
+
20
+
21
+ attr_accessor :clauses
22
+
23
+ class Parameters
24
+ def initialize(hash = nil)
25
+ @parameters = (hash || {})
26
+ end
27
+
28
+ def to_hash
29
+ @parameters
30
+ end
31
+
32
+ def copy
33
+ self.class.new(@parameters.dup)
34
+ end
35
+
36
+ def add_param(key, value)
37
+ free_param_key(key).tap do |k|
38
+ @parameters[k.freeze] = value
39
+ end
40
+ end
41
+
42
+ def remove_param(key)
43
+ @parameters.delete(key.to_sym)
44
+ end
45
+
46
+ def add_params(params)
47
+ params.map do |key, value|
48
+ add_param(key, value)
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def free_param_key(key)
55
+ k = key.to_sym
56
+
57
+ return k if !@parameters.key?(k)
58
+
59
+ i = 2
60
+ i += 1 while @parameters.key?("#{key}#{i}".to_sym)
61
+
62
+ "#{key}#{i}".to_sym
63
+ end
64
+ end
65
+
66
+ class << self
67
+ attr_accessor :pretty_cypher
68
+ end
69
+
70
+ def initialize(options = {})
71
+ @options = options
72
+ @clauses = []
73
+ @_params = {}
74
+ @params = Parameters.new
75
+ end
76
+
77
+ def inspect
78
+ "#<Query CYPHER: #{ANSI::YELLOW}#{to_cypher.inspect}#{ANSI::CLEAR}>"
79
+ end
80
+
81
+ # @method start *args
82
+ # START clause
83
+ # @return [Query]
84
+
85
+ # @method match *args
86
+ # MATCH clause
87
+ # @return [Query]
88
+
89
+ # @method optional_match *args
90
+ # OPTIONAL MATCH clause
91
+ # @return [Query]
92
+
93
+ # @method using *args
94
+ # USING clause
95
+ # @return [Query]
96
+
97
+ # @method where *args
98
+ # WHERE clause
99
+ # @return [Query]
100
+
101
+ # @method with *args
102
+ # WITH clause
103
+ # @return [Query]
104
+
105
+ # @method with_distinct *args
106
+ # WITH clause with DISTINCT specified
107
+ # @return [Query]
108
+
109
+ # @method order *args
110
+ # ORDER BY clause
111
+ # @return [Query]
112
+
113
+ # @method limit *args
114
+ # LIMIT clause
115
+ # @return [Query]
116
+
117
+ # @method skip *args
118
+ # SKIP clause
119
+ # @return [Query]
120
+
121
+ # @method set *args
122
+ # SET clause
123
+ # @return [Query]
124
+
125
+ # @method remove *args
126
+ # REMOVE clause
127
+ # @return [Query]
128
+
129
+ # @method unwind *args
130
+ # UNWIND clause
131
+ # @return [Query]
132
+
133
+ # @method return *args
134
+ # RETURN clause
135
+ # @return [Query]
136
+
137
+ # @method create *args
138
+ # CREATE clause
139
+ # @return [Query]
140
+
141
+ # @method create_unique *args
142
+ # CREATE UNIQUE clause
143
+ # @return [Query]
144
+
145
+ # @method merge *args
146
+ # MERGE clause
147
+ # @return [Query]
148
+
149
+ # @method on_create_set *args
150
+ # ON CREATE SET clause
151
+ # @return [Query]
152
+
153
+ # @method on_match_set *args
154
+ # ON MATCH SET clause
155
+ # @return [Query]
156
+
157
+ # @method delete *args
158
+ # DELETE clause
159
+ # @return [Query]
160
+
161
+ # @method detach_delete *args
162
+ # DETACH DELETE clause
163
+ # @return [Query]
164
+
165
+ METHODS = %w[start match optional_match call using where create create_unique merge set on_create_set on_match_set remove unwind delete detach_delete with with_distinct return order skip limit] # rubocop:disable Metrics/LineLength
166
+ BREAK_METHODS = %(with with_distinct call)
167
+
168
+ CLAUSIFY_CLAUSE = proc { |method| const_get(method.to_s.split('_').map(&:capitalize).join + 'Clause') }
169
+ CLAUSES = METHODS.map(&CLAUSIFY_CLAUSE)
170
+
171
+ METHODS.each_with_index do |clause, i|
172
+ clause_class = CLAUSES[i]
173
+
174
+ DEFINED_CLAUSES[clause.to_sym] = clause_class
175
+ define_method(clause) do |*args|
176
+ result = build_deeper_query(clause_class, args)
177
+
178
+ BREAK_METHODS.include?(clause) ? result.break : result
179
+ end
180
+ end
181
+
182
+ alias offset skip
183
+ alias order_by order
184
+
185
+ # Clears out previous order clauses and allows only for those specified by args
186
+ def reorder(*args)
187
+ query = copy
188
+
189
+ query.remove_clause_class(OrderClause)
190
+ query.order(*args)
191
+ end
192
+
193
+ # Works the same as the #where method, but the clause is surrounded by a
194
+ # Cypher NOT() function
195
+ def where_not(*args)
196
+ build_deeper_query(WhereClause, args, not: true)
197
+ end
198
+
199
+ # Works the same as the #set method, but when given a nested array it will set properties rather than setting entire objects
200
+ # @example
201
+ # # Creates a query representing the cypher: MATCH (n:Person) SET n.age = 19
202
+ # Query.new.match(n: :Person).set_props(n: {age: 19})
203
+ def set_props(*args) # rubocop:disable Naming/AccessorMethodName
204
+ build_deeper_query(SetClause, args, set_props: true)
205
+ end
206
+
207
+ # Allows what's been built of the query so far to be frozen and the rest built anew. Can be called multiple times in a string of method calls
208
+ # @example
209
+ # # Creates a query representing the cypher: MATCH (q:Person), r:Car MATCH (p: Person)-->q
210
+ # Query.new.match(q: Person).match('r:Car').break.match('(p: Person)-->q')
211
+ def break
212
+ build_deeper_query(nil)
213
+ end
214
+
215
+ # Allows for the specification of values for params specified in query
216
+ # @example
217
+ # # Creates a query representing the cypher: MATCH (q: Person {id: $id})
218
+ # # Calls to params don't affect the cypher query generated, but the params will be
219
+ # # Passed down when the query is made
220
+ # Query.new.match('(q: Person {id: $id})').params(id: 12)
221
+ #
222
+ def params(args)
223
+ copy.tap { |new_query| new_query.instance_variable_get('@params'.freeze).add_params(args) }
224
+ end
225
+
226
+ def unwrapped
227
+ @_unwrapped_obj = true
228
+ self
229
+ end
230
+
231
+ def unwrapped?
232
+ !!@_unwrapped_obj
233
+ end
234
+
235
+ def response
236
+ return @response if @response
237
+
238
+ @response = ActiveGraph::Base.query(self, wrap: !unwrapped?)
239
+ end
240
+
241
+ def raise_if_cypher_error!(response)
242
+ response.raise_cypher_error if response.respond_to?(:error?) && response.error?
243
+ end
244
+
245
+ def match_nodes(hash, optional_match = false)
246
+ hash.inject(self) do |query, (variable, node_object)|
247
+ neo_id = (node_object.respond_to?(:neo_id) ? node_object.neo_id : node_object)
248
+
249
+ match_method = optional_match ? :optional_match : :match
250
+ query.send(match_method, variable).where(variable => {neo_id: neo_id})
251
+ end
252
+ end
253
+
254
+ def optional_match_nodes(hash)
255
+ match_nodes(hash, true)
256
+ end
257
+
258
+ include Enumerable
259
+
260
+ def count(var = nil)
261
+ v = var.nil? ? '*' : var
262
+ pluck("count(#{v})").first
263
+ end
264
+
265
+ def each
266
+ response.each { |object| yield object }
267
+ end
268
+
269
+ # @method to_a
270
+ # Class is Enumerable. Each yield is a Hash with the key matching the variable returned and the value being the value for that key from the response
271
+ # @return [Array]
272
+ # @raise [ActiveGraph::Server::CypherResponse::ResponseError] Raises errors from neo4j server
273
+
274
+
275
+ # Executes a query without returning the result
276
+ # @return [Boolean] true if successful
277
+ # @raise [ActiveGraph::Server::CypherResponse::ResponseError] Raises errors from neo4j server
278
+ def exec
279
+ response
280
+
281
+ true
282
+ end
283
+
284
+ # Return the specified columns as an array.
285
+ # If one column is specified, a one-dimensional array is returned with the values of that column
286
+ # If two columns are specified, a n-dimensional array is returned with the values of those columns
287
+ #
288
+ # @example
289
+ # Query.new.match(n: :Person).return(p: :name}.pluck(p: :name) # => Array of names
290
+ # @example
291
+ # Query.new.match(n: :Person).return(p: :name}.pluck('p, DISTINCT p.name') # => Array of [node, name] pairs
292
+ #
293
+ def pluck(*columns)
294
+ fail ArgumentError, 'No columns specified for Query#pluck' if columns.size.zero?
295
+
296
+ query = return_query(columns)
297
+ columns = query.response.keys
298
+
299
+ if columns.size == 1
300
+ column = columns[0]
301
+ query.map { |row| row[column] }
302
+ else
303
+ query.map { |row| columns.map { |column| row[column] } }
304
+ end
305
+ end
306
+
307
+ def return_query(columns)
308
+ query = copy
309
+ query.remove_clause_class(ReturnClause)
310
+
311
+ query.return(*columns)
312
+ end
313
+
314
+ # Returns a CYPHER query string from the object query representation
315
+ # @example
316
+ # Query.new.match(p: :Person).where(p: {age: 30}) # => "MATCH (p:Person) WHERE p.age = 30
317
+ #
318
+ # @return [String] Resulting cypher query string
319
+ EMPTY = ' '
320
+ NEWLINE = "\n"
321
+ def to_cypher(options = {})
322
+ join_string = options[:pretty] ? NEWLINE : EMPTY
323
+
324
+ cypher_string = partitioned_clauses.map do |clauses|
325
+ clauses_by_class = clauses.group_by(&:class)
326
+
327
+ cypher_parts = CLAUSES.map do |clause_class|
328
+ clause_class.to_cypher(clauses, options[:pretty]) if clauses = clauses_by_class[clause_class]
329
+ end.compact
330
+
331
+ cypher_parts.join(join_string).tap(&:strip!)
332
+ end.join(join_string)
333
+
334
+ cypher_string = "CYPHER #{@options[:parser]} #{cypher_string}" if @options[:parser]
335
+ cypher_string.tap(&:strip!)
336
+ end
337
+ alias cypher to_cypher
338
+
339
+ def pretty_cypher
340
+ to_cypher(pretty: true)
341
+ end
342
+
343
+ def context
344
+ @options[:context]
345
+ end
346
+
347
+ def parameters
348
+ to_cypher
349
+ merge_params
350
+ end
351
+
352
+ def partitioned_clauses
353
+ @partitioned_clauses ||= PartitionedClauses.new(@clauses)
354
+ end
355
+
356
+ def print_cypher
357
+ puts to_cypher(pretty: true).gsub(/\e[^m]+m/, '')
358
+ end
359
+
360
+ # Returns a CYPHER query specifying the union of the callee object's query and the argument's query
361
+ #
362
+ # @example
363
+ # # Generates cypher: MATCH (n:Person) UNION MATCH (o:Person) WHERE o.age = 10
364
+ # q = ActiveGraph::Core::Query.new.match(o: :Person).where(o: {age: 10})
365
+ # result = ActiveGraph::Core::Query.new.match(n: :Person).union_cypher(q)
366
+ #
367
+ # @param other [Query] Second half of UNION
368
+ # @param options [Hash] Specify {all: true} to use UNION ALL
369
+ # @return [String] Resulting UNION cypher query string
370
+ def union_cypher(other, options = {})
371
+ "#{to_cypher} UNION#{options[:all] ? ' ALL' : ''} #{other.to_cypher}"
372
+ end
373
+
374
+ def &(other)
375
+ self.class.new.tap do |new_query|
376
+ new_query.options = options.merge(other.options)
377
+ new_query.clauses = clauses + other.clauses
378
+ end.params(other._params)
379
+ end
380
+
381
+ def copy
382
+ dup.tap do |query|
383
+ to_cypher
384
+ query.instance_variable_set('@params'.freeze, @params.copy)
385
+ query.instance_variable_set('@partitioned_clauses'.freeze, nil)
386
+ query.instance_variable_set('@response'.freeze, nil)
387
+ end
388
+ end
389
+
390
+ def clause?(method)
391
+ clause_class = DEFINED_CLAUSES[method] || CLAUSIFY_CLAUSE.call(method)
392
+ clauses.any? { |clause| clause.is_a?(clause_class) }
393
+ end
394
+
395
+ protected
396
+
397
+ attr_accessor :options, :_params
398
+
399
+ def add_clauses(clauses)
400
+ @clauses += clauses
401
+ end
402
+
403
+ def remove_clause_class(clause_class)
404
+ @clauses = @clauses.reject { |clause| clause.is_a?(clause_class) }
405
+ end
406
+
407
+ private
408
+
409
+ def build_deeper_query(clause_class, args = {}, options = {})
410
+ copy.tap do |new_query|
411
+ new_query.add_clauses [nil] if [nil, WithClause].include?(clause_class)
412
+ new_query.add_clauses clause_class.from_args(args, new_query.instance_variable_get('@params'.freeze), options) if clause_class
413
+ end
414
+ end
415
+
416
+ class PartitionedClauses
417
+ def initialize(clauses)
418
+ @clauses = clauses
419
+ @partitioning = [[]]
420
+ end
421
+
422
+ include Enumerable
423
+
424
+ def each
425
+ generate_partitioning!
426
+
427
+ @partitioning.each { |partition| yield partition }
428
+ end
429
+
430
+ def generate_partitioning!
431
+ @partitioning = [[]]
432
+
433
+ @clauses.each do |clause|
434
+ if clause.nil? && !fresh_partition?
435
+ @partitioning << []
436
+ elsif clause_is_order_or_limit_directly_following_with_or_order?(clause)
437
+ second_to_last << clause
438
+ elsif clause_is_with_following_order_or_limit?(clause)
439
+ second_to_last << clause
440
+ second_to_last.sort_by! { |c| c.is_a?(::ActiveGraph::Core::QueryClauses::OrderClause) ? 1 : 0 }
441
+ else
442
+ @partitioning.last << clause
443
+ end
444
+ end
445
+ end
446
+
447
+ private
448
+
449
+ def fresh_partition?
450
+ @partitioning.last == []
451
+ end
452
+
453
+ def second_to_last
454
+ @partitioning[-2]
455
+ end
456
+
457
+ def clause_is_order_or_limit_directly_following_with_or_order?(clause)
458
+ self.class.clause_is_order_or_limit?(clause) &&
459
+ @partitioning[-2] &&
460
+ @partitioning[-1].empty? &&
461
+ (@partitioning[-2].last.is_a?(::ActiveGraph::Core::QueryClauses::WithClause) ||
462
+ @partitioning[-2].last.is_a?(::ActiveGraph::Core::QueryClauses::OrderClause))
463
+ end
464
+
465
+ def clause_is_with_following_order_or_limit?(clause)
466
+ clause.is_a?(::ActiveGraph::Core::QueryClauses::WithClause) &&
467
+ @partitioning[-2] && @partitioning[-2].any? { |c| self.class.clause_is_order_or_limit?(c) }
468
+ end
469
+
470
+ class << self
471
+ def clause_is_order_or_limit?(clause)
472
+ clause.is_a?(::ActiveGraph::Core::QueryClauses::OrderClause) ||
473
+ clause.is_a?(::ActiveGraph::Core::QueryClauses::LimitClause)
474
+ end
475
+ end
476
+ end
477
+
478
+ # SHOULD BE DEPRECATED
479
+ def merge_params
480
+ @merge_params_base ||= @clauses.compact.inject({}) { |params, clause| params.merge!(clause.params) }
481
+ @params.to_hash.merge(@merge_params_base)
482
+ end
483
+ end
484
+ end
485
+ end