graphql 2.0.4 → 2.0.7

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 823ea48d61ed1b30e6ad87a6817f80206b9c5d8865f6350cbd142c9798162e37
4
- data.tar.gz: 8e5f383cfe80418109b0f424d06ab7a78c72255d167f21f32b6c8f6bcef10610
3
+ metadata.gz: 8b34cec1ffb7358b467f2fb7c00134db084793d9eb1ebe22f38f351435607c9b
4
+ data.tar.gz: d15754c440968f51715c0a71f3c23ef1c5fd075793c0de11aae6477976b5aa14
5
5
  SHA512:
6
- metadata.gz: 95e9de185715e1b2a752ecaefdd75e8b4daad8afd4125429d58627972805d759c3a842a58bec29b0b5ee1aba5e1d06a6cb1aed9bd1881653d710f71fe4edfdc7
7
- data.tar.gz: 99ff2f59d77ee74ba4c1af7fcf097afb5415b6d18a95c1a2527413d6f782f8fa6238aafa96430d2d7332f1177e64b70e5aa2eac1557d042ede9eb4a33466b8b5
6
+ metadata.gz: f2ef024af0f5e1e261fe7b998e27d664df5f92127ffb2874135a6d7b1662763a4e2661634dd46cc67dd624b9fc8291ce1bd3488a460566bc346f9cd6edc86b43
7
+ data.tar.gz: 635165d9129c2c49510fd481a2161cf425f91ff016bcdb28b8955d1c818d66885a6f7d90c5210e4433ada541d6e116b3137cb12dbdcc44c32229ac564d266b64
@@ -47,7 +47,7 @@ module Graphql
47
47
  #
48
48
  # Accept a `--batch` option which adds `GraphQL::Batch` setup.
49
49
  #
50
- # Use `--no-graphiql` to skip `graphiql-rails` installation.
50
+ # Use `--skip-graphiql` to skip `graphiql-rails` installation.
51
51
  #
52
52
  # TODO: also add base classes
53
53
  class InstallGenerator < Rails::Generators::Base
@@ -59,6 +59,13 @@ module GraphQL
59
59
  end
60
60
 
61
61
  if next_results.any?
62
+ # Any pending data loader jobs may populate the
63
+ # resutl arrays or result hashes accumulated in
64
+ # `next_results``. Run those **to completion**
65
+ # before continuing to resolve `next_results`.
66
+ # (Just `.append_job` doesn't work if any pending
67
+ # jobs require multiple passes.)
68
+ dataloader.run
62
69
  dataloader.append_job { resolve(next_results, dataloader) }
63
70
  end
64
71
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/execution/lazy/lazy_method_map"
3
- require "graphql/execution/lazy/resolve"
4
3
 
5
4
  module GraphQL
6
5
  module Execution
@@ -27,7 +27,7 @@ module GraphQL
27
27
  end
28
28
  field :of_type, GraphQL::Schema::LateBoundType.new("__Type")
29
29
 
30
- field :specified_by_url, String
30
+ field :specifiedByURL, String, resolver_method: :specified_by_url
31
31
 
32
32
  def specified_by_url
33
33
  if object.kind.scalar?
@@ -29,7 +29,7 @@ fragment FullType on __Type {
29
29
  kind
30
30
  name
31
31
  description
32
- #{include_specified_by_url ? "specifiedByUrl" : ""}
32
+ #{include_specified_by_url ? "specifiedByURL" : ""}
33
33
  fields(includeDeprecated: true) {
34
34
  name
35
35
  description
@@ -267,12 +267,16 @@ module GraphQL
267
267
  end
268
268
 
269
269
  def print_field_definitions(fields)
270
- out = " {\n".dup
271
- fields.each.with_index do |field, i|
272
- out << print_description(field, indent: ' ', first_in_block: i == 0)
273
- out << " #{print_field_definition(field)}\n"
270
+ if fields.empty?
271
+ ""
272
+ else
273
+ out = " {\n".dup
274
+ fields.each.with_index do |field, i|
275
+ out << print_description(field, indent: ' ', first_in_block: i == 0)
276
+ out << " #{print_field_definition(field)}\n"
277
+ end
278
+ out << "}"
274
279
  end
275
- out << "}"
276
280
  end
277
281
 
278
282
  def print_directives(directives)
@@ -35,9 +35,11 @@ module GraphQL
35
35
  def load_nodes
36
36
  @nodes ||= begin
37
37
  sliced_nodes = if before && after
38
- items[index_from_cursor(after)..index_from_cursor(before)-1] || []
38
+ end_idx = index_from_cursor(before)-1
39
+ end_idx < 0 ? [] : items[index_from_cursor(after)..end_idx] || []
39
40
  elsif before
40
- items[0..index_from_cursor(before)-2] || []
41
+ end_idx = index_from_cursor(before)-2
42
+ end_idx < 0 ? [] : items[0..end_idx] || []
41
43
  elsif after
42
44
  items[index_from_cursor(after)..-1] || []
43
45
  else
@@ -28,6 +28,9 @@ module GraphQL
28
28
  # @return [String] Method or hash key on the underlying object to look up
29
29
  attr_reader :method_str
30
30
 
31
+ attr_reader :hash_key
32
+ attr_reader :dig_keys
33
+
31
34
  # @return [Symbol] The method on the type to look up
32
35
  def resolver_method
33
36
  if @resolver_class
@@ -243,14 +246,16 @@ module GraphQL
243
246
  end
244
247
  end
245
248
 
246
- # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work)
247
- method_name = method || hash_key || name_s
249
+ method_name = method || name_s
248
250
  @dig_keys = dig
249
- resolver_method ||= name_s.to_sym
251
+ if hash_key
252
+ @hash_key = hash_key
253
+ @hash_key_str = hash_key.to_s
254
+ end
250
255
 
251
256
  @method_str = -method_name.to_s
252
257
  @method_sym = method_name.to_sym
253
- @resolver_method = resolver_method
258
+ @resolver_method = (resolver_method || name_s).to_sym
254
259
  @complexity = complexity
255
260
  @return_type_expr = type
256
261
  @return_type_null = if !null.nil?
@@ -635,14 +640,15 @@ module GraphQL
635
640
  obj = @resolver_class.new(object: obj, context: query_ctx, field: self)
636
641
  end
637
642
 
638
- # Find a way to resolve this field, checking:
639
- #
640
- # - A method on the type instance;
641
- # - Hash keys, if the wrapped object is a hash;
642
- # - A method on the wrapped object;
643
- # - Or, raise not implemented.
644
- #
645
- if obj.respond_to?(resolver_method)
643
+ inner_object = obj.object
644
+
645
+ if defined?(@hash_key)
646
+ inner_object.fetch(@hash_key) {
647
+ inner_object[@hash_key_str]
648
+ }
649
+ elsif @dig_keys
650
+ inner_object.dig(*@dig_keys)
651
+ elsif obj.respond_to?(resolver_method)
646
652
  method_to_call = resolver_method
647
653
  method_receiver = obj
648
654
  # Call the method with kwargs, if there are any
@@ -651,30 +657,27 @@ module GraphQL
651
657
  else
652
658
  obj.public_send(resolver_method)
653
659
  end
654
- elsif obj.object.is_a?(Hash)
655
- inner_object = obj.object
656
- if @dig_keys
657
- inner_object.dig(*@dig_keys)
658
- elsif inner_object.key?(@method_sym)
660
+ elsif inner_object.is_a?(Hash)
661
+ if inner_object.key?(@method_sym)
659
662
  inner_object[@method_sym]
660
663
  else
661
664
  inner_object[@method_str]
662
665
  end
663
- elsif obj.object.respond_to?(@method_sym)
666
+ elsif inner_object.respond_to?(@method_sym)
664
667
  method_to_call = @method_sym
665
668
  method_receiver = obj.object
666
669
  if ruby_kwargs.any?
667
- obj.object.public_send(@method_sym, **ruby_kwargs)
670
+ inner_object.public_send(@method_sym, **ruby_kwargs)
668
671
  else
669
- obj.object.public_send(@method_sym)
672
+ inner_object.public_send(@method_sym)
670
673
  end
671
674
  else
672
675
  raise <<-ERR
673
676
  Failed to implement #{@owner.graphql_name}.#{@name}, tried:
674
677
 
675
678
  - `#{obj.class}##{resolver_method}`, which did not exist
676
- - `#{obj.object.class}##{@method_sym}`, which did not exist
677
- - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash
679
+ - `#{inner_object.class}##{@method_sym}`, which did not exist
680
+ - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{inner_object}`, but it wasn't a Hash
678
681
 
679
682
  To implement this field, define one of the methods above (and check for typos)
680
683
  ERR
@@ -142,7 +142,7 @@ module GraphQL
142
142
  Class.new(GraphQL::Schema::Scalar) do
143
143
  graphql_name(type["name"])
144
144
  description(type["description"])
145
- specified_by_url(type["specifiedByUrl"])
145
+ specified_by_url(type["specifiedByURL"])
146
146
  end
147
147
  end
148
148
  when "UNION"
@@ -148,7 +148,19 @@ module GraphQL
148
148
  end
149
149
  elsif defined?(@resolver_class) && @resolver_class
150
150
  all_defns = {}
151
- all_defns.merge!(@resolver_class.own_field_arguments)
151
+ @resolver_class.all_field_argument_definitions.each do |arg_defn|
152
+ key = arg_defn.graphql_name
153
+ case (current_value = all_defns[key])
154
+ when nil
155
+ all_defns[key] = arg_defn
156
+ when Array
157
+ current_value << arg_defn
158
+ when GraphQL::Schema::Argument
159
+ all_defns[key] = [current_value, arg_defn]
160
+ else
161
+ raise "Invariant: Unexpected argument definition, #{current_value.class}: #{current_value.inspect}"
162
+ end
163
+ end
152
164
  all_defns.merge!(own_arguments)
153
165
  else
154
166
  all_defns = own_arguments
@@ -72,7 +72,7 @@ module GraphQL
72
72
  def add_field(field_defn, method_conflict_warning: field_defn.method_conflict_warning?)
73
73
  # Check that `field_defn.original_name` equals `resolver_method` and `method_sym` --
74
74
  # that shows that no override value was given manually.
75
- if method_conflict_warning && CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.original_name == field_defn.method_sym
75
+ if method_conflict_warning && CONFLICT_FIELD_NAMES.include?(field_defn.resolver_method) && field_defn.original_name == field_defn.resolver_method && field_defn.original_name == field_defn.method_sym && field_defn.hash_key.nil? && field_defn.dig_keys.nil?
76
76
  warn(conflict_field_name_warning(field_defn))
77
77
  end
78
78
  prev_defn = own_fields[field_defn.name]
@@ -92,6 +92,10 @@ module GraphQL
92
92
  dummy.own_arguments
93
93
  end
94
94
 
95
+ def all_field_argument_definitions
96
+ dummy.all_argument_definitions
97
+ end
98
+
95
99
  # Also apply this argument to the input type:
96
100
  def argument(*args, own_argument: false, **kwargs, &block)
97
101
  it = input_type # make sure any inherited arguments are already added to it
@@ -73,7 +73,7 @@ module GraphQL
73
73
  context.schema.after_lazy(ready_val) do |is_ready, ready_early_return|
74
74
  if ready_early_return
75
75
  if is_ready != false
76
- raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{ready_early_return.inspect}]"
76
+ raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{ready_early_return.inspect}]"
77
77
  else
78
78
  ready_early_return
79
79
  end
@@ -216,8 +216,8 @@ module GraphQL
216
216
  get_argument(name, context)
217
217
  end
218
218
 
219
- def own_field_arguments
220
- own_arguments
219
+ def all_field_argument_definitions
220
+ all_argument_definitions
221
221
  end
222
222
 
223
223
  # Default `:resolve` set below.
@@ -55,7 +55,9 @@ module GraphQL
55
55
  if visible_item.nil?
56
56
  visible_item = item
57
57
  else
58
- raise Schema::DuplicateNamesError, "Found two visible definitions for `#{item.path}`: #{visible_item.inspect}, #{item.inspect}"
58
+ raise DuplicateNamesError.new(
59
+ duplicated_name: item.path, duplicated_definition_1: visible_item.inspect, duplicated_definition_2: item.inspect
60
+ )
59
61
  end
60
62
  end
61
63
  end
@@ -362,7 +364,9 @@ module GraphQL
362
364
  if @reachable_type_set.add?(type)
363
365
  type_by_name = rt_hash[type.graphql_name] ||= type
364
366
  if type_by_name != type
365
- raise DuplicateNamesError, "Found two visible type definitions for `#{type.graphql_name}`: #{type.inspect}, #{type_by_name.inspect}"
367
+ raise DuplicateNamesError.new(
368
+ duplicated_name: type.graphql_name, duplicated_definition_1: type.inspect, duplicated_definition_2: type_by_name.inspect
369
+ )
366
370
  end
367
371
  if type.kind.input_object?
368
372
  # recurse into visible arguments
@@ -73,14 +73,16 @@ module GraphQL
73
73
  extend GraphQL::Schema::Member::HasAstNode
74
74
  extend GraphQL::Schema::FindInheritedValue
75
75
 
76
- class DuplicateTypeNamesError < GraphQL::Error
77
- def initialize(type_name:, first_definition:, second_definition:, path:)
78
- super("Multiple definitions for `#{type_name}`. Previously found #{first_definition.inspect} (#{first_definition.class}), then found #{second_definition.inspect} (#{second_definition.class}) at #{path.join(".")}")
76
+ class DuplicateNamesError < GraphQL::Error
77
+ attr_reader :duplicated_name
78
+ def initialize(duplicated_name:, duplicated_definition_1:, duplicated_definition_2:)
79
+ @duplicated_name = duplicated_name
80
+ super(
81
+ "Found two visible definitions for `#{duplicated_name}`: #{duplicated_definition_1}, #{duplicated_definition_2}"
82
+ )
79
83
  end
80
84
  end
81
85
 
82
- class DuplicateNamesError < GraphQL::Error; end
83
-
84
86
  class UnresolvedLateBoundTypeError < GraphQL::Error
85
87
  attr_reader :type
86
88
  def initialize(type:)
@@ -225,7 +227,9 @@ module GraphQL
225
227
  if visible_t.nil?
226
228
  visible_t = t
227
229
  else
228
- raise DuplicateNamesError, "Found two visible type definitions for `#{k}`: #{visible_t.inspect}, #{t.inspect}"
230
+ raise DuplicateNamesError.new(
231
+ duplicated_name: k, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
232
+ )
229
233
  end
230
234
  end
231
235
  end
@@ -252,7 +256,9 @@ module GraphQL
252
256
  if visible_t.nil?
253
257
  visible_t = t
254
258
  else
255
- raise DuplicateNamesError, "Found two visible type definitions for `#{type_name}`: #{visible_t.inspect}, #{t.inspect}"
259
+ raise DuplicateNamesError.new(
260
+ duplicated_name: type_name, duplicated_definition_1: visible_t.inspect, duplicated_definition_2: t.inspect
261
+ )
256
262
  end
257
263
  end
258
264
  end
@@ -932,6 +938,7 @@ module GraphQL
932
938
  {
933
939
  backtrace: ctx[:backtrace],
934
940
  tracers: ctx[:tracers],
941
+ dataloader: ctx[:dataloader],
935
942
  }
936
943
  else
937
944
  {}
@@ -976,7 +983,9 @@ module GraphQL
976
983
  if !defined?(@subscription_extension_added) && subscription && self.subscriptions
977
984
  @subscription_extension_added = true
978
985
  subscription.all_field_definitions.each do |field|
979
- field.extension(Subscriptions::DefaultSubscriptionResolveExtension)
986
+ if !field.extensions.any? { |ext| ext.is_a?(Subscriptions::DefaultSubscriptionResolveExtension) }
987
+ field.extension(Subscriptions::DefaultSubscriptionResolveExtension)
988
+ end
980
989
  end
981
990
  end
982
991
  end
@@ -39,12 +39,14 @@ module GraphQL
39
39
  end
40
40
 
41
41
  # @param schema [Class] the GraphQL schema this manager belongs to
42
- def initialize(schema:, broadcast: false, default_broadcastable: false, **rest)
42
+ # @param validate_update [Boolean] If false, then validation is skipped when executing updates
43
+ def initialize(schema:, validate_update: true, broadcast: false, default_broadcastable: false, **rest)
43
44
  if broadcast
44
45
  schema.query_analyzer(Subscriptions::BroadcastAnalyzer)
45
46
  end
46
47
  @default_broadcastable = default_broadcastable
47
48
  @schema = schema
49
+ @validate_update = validate_update
48
50
  end
49
51
 
50
52
  # @return [Boolean] Used when fields don't have `broadcastable:` explicitly set
@@ -117,14 +119,16 @@ module GraphQL
117
119
  variables = query_data.fetch(:variables)
118
120
  context = query_data.fetch(:context)
119
121
  operation_name = query_data.fetch(:operation_name)
120
- result = @schema.execute(
122
+ execute_options = {
121
123
  query: query_string,
122
124
  context: context,
123
125
  subscription_topic: event.topic,
124
126
  operation_name: operation_name,
125
127
  variables: variables,
126
128
  root_value: object,
127
- )
129
+ }
130
+ execute_options[:validate] = validate_update?(**execute_options)
131
+ result = @schema.execute(**execute_options)
128
132
  subscriptions_context = result.context.namespace(:subscriptions)
129
133
  if subscriptions_context[:no_update]
130
134
  result = nil
@@ -142,6 +146,14 @@ module GraphQL
142
146
  result
143
147
  end
144
148
 
149
+ # Define this method to customize whether to validate
150
+ # this subscription when executing an update.
151
+ #
152
+ # @return [Boolean] defaults to `true`, or false if `validate: false` is provided.
153
+ def validate_update?(query:, context:, root_value:, subscription_topic:, operation_name:, variables:)
154
+ @validate_update
155
+ end
156
+
145
157
  # Run the update query for this subscription and deliver it
146
158
  # @see {#execute_update}
147
159
  # @see {#deliver}
@@ -17,15 +17,21 @@ module GraphQL
17
17
  def platform_trace(platform_key, key, data)
18
18
  tracer.trace(platform_key, service: service_name) do |span|
19
19
  span.span_type = 'custom'
20
+ if defined?(Datadog::Tracing::Metadata::Ext) # Introduced in ddtrace 1.0
21
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_COMPONENT, 'graphql')
22
+ span.set_tag(Datadog::Tracing::Metadata::Ext::TAG_OPERATION, key)
23
+ end
20
24
 
21
25
  if key == 'execute_multiplex'
22
26
  operations = data[:multiplex].queries.map(&:selected_operation_name).join(', ')
23
- span.resource = if operations.empty?
27
+
28
+ resource = if operations.empty?
24
29
  first_query = data[:multiplex].queries.first
25
30
  fallback_transaction_name(first_query && first_query.context)
26
31
  else
27
32
  operations
28
33
  end
34
+ span.resource = resource if resource
29
35
 
30
36
  # For top span of query, set the analytics sample rate tag, if available.
31
37
  if analytics_enabled?
@@ -14,6 +14,7 @@ module GraphQL
14
14
  # own Date type.
15
15
  class ISO8601Date < GraphQL::Schema::Scalar
16
16
  description "An ISO 8601-encoded date"
17
+ specified_by_url "https://tools.ietf.org/html/rfc3339"
17
18
 
18
19
  # @param value [Date,Time,DateTime,String]
19
20
  # @return [String]
@@ -22,7 +23,7 @@ module GraphQL
22
23
  end
23
24
 
24
25
  # @param str_value [String, Date, DateTime, Time]
25
- # @return [Date]
26
+ # @return [Date, nil]
26
27
  def self.coerce_input(value, ctx)
27
28
  if value.is_a?(::Date)
28
29
  value
@@ -30,6 +31,8 @@ module GraphQL
30
31
  value.to_date
31
32
  elsif value.is_a?(::Time)
32
33
  value.to_date
34
+ elsif value.nil?
35
+ nil
33
36
  else
34
37
  Date.iso8601(value)
35
38
  end
@@ -17,6 +17,7 @@ module GraphQL
17
17
  # own DateTime type.
18
18
  class ISO8601DateTime < GraphQL::Schema::Scalar
19
19
  description "An ISO 8601-encoded datetime"
20
+ specified_by_url "https://tools.ietf.org/html/rfc3339"
20
21
 
21
22
  # It's not compatible with Rails' default,
22
23
  # i.e. ActiveSupport::JSON::Encoder.time_precision (3 by default)
@@ -10,6 +10,8 @@ module GraphQL
10
10
  # so you can extend your own `BaseObject` instead of `GraphQL::Schema::Object`.
11
11
  #
12
12
  # @example Implementation a connection and edge
13
+ # class BaseObject < GraphQL::Schema::Object; end
14
+ #
13
15
  # # Given some object in your app ...
14
16
  # class Types::Post < BaseObject
15
17
  # end
@@ -20,14 +22,22 @@ module GraphQL
20
22
  #
21
23
  # # Then extend them for the object in your app
22
24
  # class Types::PostEdge < Types::BaseEdge
23
- # node_type(Types::Post)
25
+ # node_type Types::Post
24
26
  # end
27
+ #
25
28
  # class Types::PostConnection < Types::BaseConnection
26
- # edge_type(Types::PostEdge)
27
- # edges_nullable(true)
28
- # edge_nullable(true)
29
- # node_nullable(true)
30
- # has_nodes_field(true)
29
+ # edge_type Types::PostEdge,
30
+ # edges_nullable: true,
31
+ # edge_nullable: true,
32
+ # node_nullable: true,
33
+ # nodes_field: true
34
+ #
35
+ # # Alternatively, you can call the class methods followed by your edge type
36
+ # # edges_nullable true
37
+ # # edge_nullable true
38
+ # # nodes_nullable true
39
+ # # has_nodes_field true
40
+ # # edge_type Types::PostEdge
31
41
  # end
32
42
  #
33
43
  # @see Relay::BaseEdge for edge types
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.0.4"
3
+ VERSION = "2.0.7"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.4
4
+ version: 2.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-21 00:00:00.000000000 Z
11
+ date: 2022-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -314,7 +314,6 @@ files:
314
314
  - lib/graphql/execution/interpreter/runtime.rb
315
315
  - lib/graphql/execution/lazy.rb
316
316
  - lib/graphql/execution/lazy/lazy_method_map.rb
317
- - lib/graphql/execution/lazy/resolve.rb
318
317
  - lib/graphql/execution/lookahead.rb
319
318
  - lib/graphql/execution/multiplex.rb
320
319
  - lib/graphql/execution_error.rb
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Execution
4
- class Lazy
5
- # Helpers for dealing with data structures containing {Lazy} instances
6
- # @api private
7
- module Resolve
8
- # Mutate `value`, replacing {Lazy} instances in place with their resolved values
9
- # @return [void]
10
-
11
- # This object can be passed like an array, but it doesn't allocate an
12
- # array until it's used.
13
- #
14
- # There's one crucial difference: you have to _capture_ the result
15
- # of `#<<`. (This _works_ with arrays but isn't required, since it has a side-effect.)
16
- # @api private
17
- module NullAccumulator
18
- def self.<<(item)
19
- [item]
20
- end
21
-
22
- def self.empty?
23
- true
24
- end
25
- end
26
-
27
- def self.resolve(value)
28
- lazies = resolve_in_place(value)
29
- deep_sync(lazies)
30
- end
31
-
32
- def self.resolve_in_place(value)
33
- acc = each_lazy(NullAccumulator, value)
34
-
35
- if acc.empty?
36
- Lazy::NullResult
37
- else
38
- Lazy.new {
39
- acc.each_with_index { |ctx, idx|
40
- acc[idx] = ctx.value.value
41
- }
42
- resolve_in_place(acc)
43
- }
44
- end
45
- end
46
-
47
- # If `value` is a collection,
48
- # add any {Lazy} instances in the collection
49
- # to `acc`
50
- # @return [void]
51
- def self.each_lazy(acc, value)
52
- case value
53
- when Hash
54
- value.each do |key, field_result|
55
- acc = each_lazy(acc, field_result)
56
- end
57
- when Array
58
- value.each do |field_result|
59
- acc = each_lazy(acc, field_result)
60
- end
61
- when Query::Context::SharedMethods
62
- field_value = value.value
63
- case field_value
64
- when Lazy
65
- acc = acc << value
66
- when Enumerable # shortcut for Hash & Array
67
- acc = each_lazy(acc, field_value)
68
- end
69
- end
70
-
71
- acc
72
- end
73
-
74
- # Traverse `val`, triggering resolution for each {Lazy}.
75
- # These {Lazy}s are expected to mutate their owner data structures
76
- # during resolution! (They're created with the `.then` calls in `resolve_in_place`).
77
- # @return [void]
78
- def self.deep_sync(val)
79
- case val
80
- when Lazy
81
- deep_sync(val.value)
82
- when Array
83
- val.each { |v| deep_sync(v.value) }
84
- when Hash
85
- val.each { |k, v| deep_sync(v.value) }
86
- end
87
- end
88
- end
89
- end
90
- end
91
- end