activegraph 11.0.0.beta.1-java

Sign up to get free protection for your applications and to get access to all the features.
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