graphql 2.5.20 → 2.6.0

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis.rb +20 -13
  3. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +3 -3
  4. data/lib/graphql/execution/field_resolve_step.rb +631 -0
  5. data/lib/graphql/execution/finalize.rb +217 -0
  6. data/lib/graphql/execution/input_values.rb +261 -0
  7. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  8. data/lib/graphql/execution/interpreter/runtime.rb +3 -2
  9. data/lib/graphql/execution/interpreter.rb +6 -9
  10. data/lib/graphql/execution/lazy.rb +1 -1
  11. data/lib/graphql/execution/load_argument_step.rb +64 -0
  12. data/lib/graphql/execution/multiplex.rb +1 -1
  13. data/lib/graphql/execution/next.rb +90 -0
  14. data/lib/graphql/execution/prepare_object_step.rb +128 -0
  15. data/lib/graphql/execution/runner.rb +410 -0
  16. data/lib/graphql/execution/selections_step.rb +91 -0
  17. data/lib/graphql/execution.rb +8 -4
  18. data/lib/graphql/execution_error.rb +5 -1
  19. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  20. data/lib/graphql/introspection/entry_points.rb +5 -1
  21. data/lib/graphql/pagination/connection.rb +2 -0
  22. data/lib/graphql/pagination/connections.rb +32 -0
  23. data/lib/graphql/query/context.rb +7 -1
  24. data/lib/graphql/query/partial.rb +18 -3
  25. data/lib/graphql/query.rb +10 -1
  26. data/lib/graphql/runtime_error.rb +6 -0
  27. data/lib/graphql/schema/argument.rb +1 -0
  28. data/lib/graphql/schema/build_from_definition.rb +22 -25
  29. data/lib/graphql/schema/directive.rb +23 -9
  30. data/lib/graphql/schema/field/connection_extension.rb +4 -37
  31. data/lib/graphql/schema/field/scope_extension.rb +18 -13
  32. data/lib/graphql/schema/field.rb +59 -62
  33. data/lib/graphql/schema/field_extension.rb +11 -8
  34. data/lib/graphql/schema/interface.rb +26 -0
  35. data/lib/graphql/schema/list.rb +5 -1
  36. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
  37. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  38. data/lib/graphql/schema/member/has_dataloader.rb +28 -0
  39. data/lib/graphql/schema/member/has_fields.rb +11 -5
  40. data/lib/graphql/schema/member.rb +5 -0
  41. data/lib/graphql/schema/non_null.rb +1 -1
  42. data/lib/graphql/schema/object.rb +1 -0
  43. data/lib/graphql/schema/resolver.rb +80 -0
  44. data/lib/graphql/schema/subscription.rb +0 -2
  45. data/lib/graphql/schema/visibility/profile.rb +68 -49
  46. data/lib/graphql/schema/wrapper.rb +7 -1
  47. data/lib/graphql/schema.rb +12 -10
  48. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  49. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  50. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  51. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  52. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  53. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  54. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -2
  55. data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
  56. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  57. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  58. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  59. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  60. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  61. data/lib/graphql/static_validation/validation_context.rb +1 -1
  62. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +34 -10
  63. data/lib/graphql/subscriptions/event.rb +1 -0
  64. data/lib/graphql/subscriptions.rb +35 -0
  65. data/lib/graphql/tracing/perfetto_trace.rb +2 -2
  66. data/lib/graphql/tracing/trace.rb +6 -0
  67. data/lib/graphql/types/relay/has_node_field.rb +10 -2
  68. data/lib/graphql/types/relay/has_nodes_field.rb +10 -2
  69. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  70. data/lib/graphql/unauthorized_error.rb +4 -0
  71. data/lib/graphql/version.rb +1 -1
  72. data/lib/graphql.rb +1 -3
  73. metadata +13 -9
  74. data/lib/graphql/execution/batching/field_compatibility.rb +0 -150
  75. data/lib/graphql/execution/batching/field_resolve_step.rb +0 -408
  76. data/lib/graphql/execution/batching/prepare_object_step.rb +0 -112
  77. data/lib/graphql/execution/batching/runner.rb +0 -352
  78. data/lib/graphql/execution/batching/selections_step.rb +0 -37
  79. data/lib/graphql/execution/batching.rb +0 -62
@@ -8,6 +8,7 @@ module GraphQL
8
8
  include GraphQL::Schema::Member::HasArguments
9
9
  include GraphQL::Schema::Member::HasArguments::FieldConfigured
10
10
  include GraphQL::Schema::Member::HasAstNode
11
+ include GraphQL::Schema::Member::HasAuthorization
11
12
  include GraphQL::Schema::Member::HasPath
12
13
  include GraphQL::Schema::Member::HasValidators
13
14
  extend GraphQL::Schema::FindInheritedValue
@@ -192,9 +193,11 @@ module GraphQL
192
193
  # @param resolver_method [Symbol] The method on the type to call to resolve this field (defaults to `name`)
193
194
  # @param connection [Boolean] `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
194
195
  # @param connection_extension [Class] The extension to add, to implement connections. If `nil`, no extension is added.
195
- # @param resolve_static [Symbol, nil] Used by {Schema.execute_batching} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
196
- # @param resolve_batch [Symbol, nil] Used by {Schema.execute_batching} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
197
- # @param resolve_each [Symbol, nil] Used by {Schema.execute_batching} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
196
+ # @param resolve_static [Symbol, true, nil] Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
197
+ # @param resolve_batch [Symbol, true, nil] Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
198
+ # @param resolve_each [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
199
+ # @param resolve_legacy_instance_method [Symbol, true, nil] Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class.
200
+ # @param dataload [Class, Hash] Shorthand for making dataloader calls
198
201
  # @param max_page_size [Integer, nil] For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
199
202
  # @param default_page_size [Integer, nil] For connections, the default number of items to return from this field, or `nil` to return unlimited results.
200
203
  # @param introspection [Boolean] If true, this field will be marked as `#introspection?` and the name may begin with `__`
@@ -217,7 +220,7 @@ module GraphQL
217
220
  # @param relay_nodes_field [Boolean] (Private, used by GraphQL-Ruby)
218
221
  # @param extras [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] Extra arguments to be injected into the resolver for this field
219
222
  # @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}.
220
- def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, resolve_static: nil, resolve_each: nil, resolve_batch: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
223
+ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CONFIGURED, comment: NOT_CONFIGURED, deprecation_reason: nil, method: nil, resolve_legacy_instance_method: nil, resolve_static: nil, resolve_each: nil, resolve_batch: nil, hash_key: nil, dig: nil, resolver_method: nil, connection: nil, max_page_size: NOT_CONFIGURED, default_page_size: NOT_CONFIGURED, scope: nil, introspection: false, camelize: true, trace: nil, complexity: nil, dataload: nil, ast_node: nil, extras: EMPTY_ARRAY, extensions: EMPTY_ARRAY, connection_extension: self.class.connection_extension, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, broadcastable: NOT_CONFIGURED, arguments: EMPTY_HASH, directives: EMPTY_HASH, validates: EMPTY_ARRAY, fallback_value: NOT_CONFIGURED, dynamic_introspection: false, &definition_block)
221
224
  if name.nil?
222
225
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
223
226
  end
@@ -267,23 +270,32 @@ module GraphQL
267
270
  @resolver_method = (resolver_method || name_s).to_sym
268
271
 
269
272
  if resolve_static
270
- @batch_mode = :resolve_static
271
- @batch_mode_key = resolve_static == true ? @method_sym : resolve_static
273
+ @execution_mode = :resolve_static
274
+ @execution_mode_key = resolve_static == true ? @method_sym : resolve_static
272
275
  elsif resolve_batch
273
- @batch_mode = :resolve_batch
274
- @batch_mode_key = resolve_batch == true ? @method_sym : resolve_batch
276
+ @execution_mode = :resolve_batch
277
+ @execution_mode_key = resolve_batch == true ? @method_sym : resolve_batch
275
278
  elsif resolve_each
276
- @batch_mode = :resolve_each
277
- @batch_mode_key = resolve_each == true ? @method_sym : resolve_each
279
+ @execution_mode = :resolve_each
280
+ @execution_mode_key = resolve_each == true ? @method_sym : resolve_each
278
281
  elsif hash_key
279
- @batch_mode = :hash_key
280
- @batch_mode_key = hash_key
282
+ @execution_mode = :hash_key
283
+ @execution_mode_key = hash_key
281
284
  elsif dig
282
- @batch_mode = :dig
283
- @batch_mode_key = dig
285
+ @execution_mode = :dig
286
+ @execution_mode_key = dig
287
+ elsif resolver_class
288
+ @execution_mode = :resolver_class
289
+ @execution_mode_key = resolver_class
290
+ elsif resolve_legacy_instance_method
291
+ @execution_mode = :resolve_legacy_instance_method
292
+ @execution_mode_key = resolve_legacy_instance_method == true ? @method_sym : resolve_legacy_instance_method
293
+ elsif dataload
294
+ @execution_mode = :dataload
295
+ @execution_mode_key = dataload
284
296
  else
285
- @batch_mode = :direct_send
286
- @batch_mode_key = @method_sym
297
+ @execution_mode = :direct_send
298
+ @execution_mode_key = @method_sym
287
299
  end
288
300
 
289
301
  @complexity = complexity
@@ -356,49 +368,8 @@ module GraphQL
356
368
  end
357
369
  end
358
370
 
359
- # Called by {Execution::Batching} to resolve this field for each of `objects`
360
- # @param field_resolve_step [Execution::Batching::FieldResolveStep] an internal metadata object from execution code
361
- # @param objects [Array<Object>] Objects returned from previously-executed fields
362
- # @param context [GraphQL::Query::Context]
363
- # @param args_hash [Hash<Symbol => Object>] Ruby-style arguments for this field
364
- # @return [Array<Object>] One field result for each of `objects`; must have the same length as `objects`
365
- # @see #initialize Use `resolve_static:`, `resolve_batch:`, `resolve_each:`, `hash_key:`, or `method:`
366
371
  # @api private
367
- def resolve_batch(field_resolve_step, objects, context, args_hash)
368
- case @batch_mode
369
- when :resolve_batch
370
- if args_hash.empty?
371
- @owner.public_send(@batch_mode_key, objects, context)
372
- else
373
- @owner.public_send(@batch_mode_key, objects, context, **args_hash)
374
- end
375
- when :resolve_static
376
- result = if args_hash.empty?
377
- @owner.public_send(@batch_mode_key, context)
378
- else
379
- @owner.public_send(@batch_mode_key, context, **args_hash)
380
- end
381
- Array.new(objects.size, result)
382
- when :resolve_each
383
- if args_hash.empty?
384
- objects.map { |o| @owner.public_send(@batch_mode_key, o, context) }
385
- else
386
- objects.map { |o| @owner.public_send(@batch_mode_key, o, context, **args_hash) }
387
- end
388
- when :hash_key
389
- objects.map { |o| o[@batch_mode_key] }
390
- when :direct_send
391
- if args_hash.empty?
392
- objects.map { |o| o.public_send(@batch_mode_key) }
393
- else
394
- objects.map { |o| o.public_send(@batch_mode_key, **args_hash) }
395
- end
396
- when :dig
397
- objects.map { |o| o.dig(*@batch_mode_key) }
398
- else
399
- raise "Batching execution for #{path} not implemented; provide `resolve_static:`, `resolve_batch:`, `hash_key:`, `method:`, or use a compatibility plug-in"
400
- end
401
- end
372
+ attr_reader :execution_mode_key, :execution_mode
402
373
 
403
374
  # Calls the definition block, if one was given.
404
375
  # This is deferred so that references to the return type
@@ -694,10 +665,9 @@ module GraphQL
694
665
  end
695
666
 
696
667
  def authorizes?(context)
697
- method(:authorized?).owner != GraphQL::Schema::Field || (
698
- (args = context.types.arguments(self)) &&
699
- (args.any? { |a| a.authorizes?(context) })
700
- )
668
+ method(:authorized?).owner != GraphQL::Schema::Field ||
669
+ ((args = context.types.arguments(self)) && (args.any? { |a| a.authorizes?(context) })) ||
670
+ (@resolver_class&.authorizes?(context)) || false
701
671
  end
702
672
 
703
673
  def authorized?(object, args, context)
@@ -954,6 +924,33 @@ ERR
954
924
  end
955
925
  end
956
926
 
927
+ public
928
+
929
+ def run_next_extensions_before_resolve(objs, args, ctx, extended, idx: 0, &block)
930
+ extension = @extensions[idx]
931
+ if extension
932
+ extension.resolve(objects: objs, arguments: args, context: ctx) do |extended_objs, extended_args, memo|
933
+ if memo
934
+ memos = extended.memos ||= {}
935
+ memos[idx] = memo
936
+ end
937
+
938
+ if (extras = extension.added_extras)
939
+ ae = extended.added_extras ||= []
940
+ ae.concat(extras)
941
+ end
942
+
943
+ extended.object = extended_objs
944
+ extended.arguments = extended_args
945
+ run_next_extensions_before_resolve(extended_objs, extended_args, ctx, extended, idx: idx + 1, &block)
946
+ end
947
+ else
948
+ yield(objs, args)
949
+ end
950
+ end
951
+
952
+ private
953
+
957
954
  def run_extensions_before_resolve(obj, args, ctx, extended, idx: 0)
958
955
  extension = @extensions[idx]
959
956
  if extension
@@ -123,15 +123,16 @@ module GraphQL
123
123
  #
124
124
  # Whatever this method returns will be used for execution.
125
125
  #
126
- # @param object [Object] The object the field is being resolved on
126
+ # @param object [Object] The object the field is being resolved on (not passed by new execution)
127
+ # @param objects [Array<Object>] The objects the field is being resolved on (passed by new execution)
127
128
  # @param arguments [Hash] Ruby keyword arguments for resolving this field
128
129
  # @param context [Query::Context] the context for this query
129
- # @yieldparam object [Object] The object to continue resolving the field on
130
+ # @yieldparam object_or_objects [Object, Array<Object>] The object or objects (new execution) to continue resolving the field on
130
131
  # @yieldparam arguments [Hash] The keyword arguments to continue resolving with
131
132
  # @yieldparam memo [Object] Any extension-specific value which will be passed to {#after_resolve} later
132
133
  # @return [Object] The return value for this field.
133
- def resolve(object:, arguments:, context:)
134
- yield(object, arguments, nil)
134
+ def resolve(object: nil, objects: nil, arguments:, context:)
135
+ yield(object.nil? ? objects : object, arguments, nil)
135
136
  end
136
137
 
137
138
  # Called after {#field} was resolved, and after any lazy values (like `Promise`s) were synced,
@@ -139,14 +140,16 @@ module GraphQL
139
140
  #
140
141
  # Whatever this hook returns will be used as the return value.
141
142
  #
142
- # @param object [Object] The object the field is being resolved on
143
+ # @param object [Object] The object the field is being resolved on (not passed by new execution)
144
+ # @param objects [Array<Object>] The object the field is being resolved on (passed by new execution)
143
145
  # @param arguments [Hash] Ruby keyword arguments for resolving this field
144
146
  # @param context [Query::Context] the context for this query
145
- # @param value [Object] Whatever the field previously returned
147
+ # @param value [Object] Whatever the field previously returned (not passed by new execution)
148
+ # @param values [Array<Object>] Whatever the field previously returned (passed by new execution)
146
149
  # @param memo [Object] The third value yielded by {#resolve}, or `nil` if there wasn't one
147
150
  # @return [Object] The return value for this field.
148
- def after_resolve(object:, arguments:, context:, value:, memo:)
149
- value
151
+ def after_resolve(object: nil, objects: nil, arguments:, context:, values: nil, value: nil, memo:)
152
+ value.nil? ? values : value
150
153
  end
151
154
  end
152
155
  end
@@ -33,6 +33,26 @@ module GraphQL
33
33
  self::DefinitionMethods.module_exec(&block)
34
34
  end
35
35
 
36
+ # Instance methods defined in this block will become class methods on objects that implement this interface.
37
+ # Use it to implement `resolve_each:`, `resolve_batch:`, and `resolve_static:` fields.
38
+ # @example
39
+ # field :thing, String, resolve_static: true
40
+ #
41
+ # resolver_methods do
42
+ # def thing
43
+ # Somehow.get.thing
44
+ # end
45
+ # end
46
+ def resolver_methods(&block)
47
+ if !defined?(@_resolver_methods)
48
+ resolver_methods_module = Module.new
49
+ @_resolver_methods = resolver_methods_module
50
+ const_set(:ResolverMethods, resolver_methods_module)
51
+ extend(self::ResolverMethods)
52
+ end
53
+ self::ResolverMethods.module_exec(&block)
54
+ end
55
+
36
56
  # @see {Schema::Warden} hides interfaces without visible implementations
37
57
  def visible?(context)
38
58
  true
@@ -79,6 +99,12 @@ module GraphQL
79
99
  if !backtrace_line
80
100
  raise "Attach interfaces using `implements(#{self})`, not `include(#{self})`"
81
101
  end
102
+
103
+ child_class.ancestors.reverse_each do |ancestor|
104
+ if ancestor.const_defined?(:ResolverMethods)
105
+ child_class.extend(ancestor::ResolverMethods)
106
+ end
107
+ end
82
108
  end
83
109
 
84
110
  super
@@ -19,7 +19,11 @@ module GraphQL
19
19
  end
20
20
 
21
21
  def to_type_signature
22
- "[#{@of_type.to_type_signature}]"
22
+ @type_signature ||= -"[#{@of_type.to_type_signature}]"
23
+ end
24
+
25
+ def authorizes?(ctx)
26
+ of_type.authorizes?(ctx)
23
27
  end
24
28
 
25
29
  # This is for introspection, where it's expected the name will be `null`
@@ -26,16 +26,6 @@ module GraphQL
26
26
  end
27
27
  end
28
28
 
29
- # Just a convenience method to point out that people should use graphql_name instead
30
- def name(new_name = nil)
31
- return super() if new_name.nil?
32
-
33
- fail(
34
- "The new name override method is `graphql_name`, not `name`. Usage: "\
35
- "graphql_name \"#{new_name}\""
36
- )
37
- end
38
-
39
29
  # Call this method to provide a new description; OR
40
30
  # call it without an argument to get the description
41
31
  # @param new_description [String]
@@ -130,7 +120,7 @@ module GraphQL
130
120
  true
131
121
  end
132
122
 
133
- def default_relay
123
+ def default_relay?
134
124
  false
135
125
  end
136
126
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ class Member
5
+ module HasAuthorization
6
+ def self.included(child_class)
7
+ child_class.include(InstanceConfigured)
8
+ end
9
+
10
+ def self.extended(child_class)
11
+ child_class.extend(ClassConfigured)
12
+ child_class.class_exec do
13
+ @authorizes = false
14
+ end
15
+ end
16
+
17
+ def authorized?(object, context)
18
+ true
19
+ end
20
+
21
+ module InstanceConfigured
22
+ def authorizes?(context)
23
+ raise RequiredImplementationMissingError, "#{self.class} must implement #authorizes?(context)"
24
+ end
25
+ end
26
+
27
+ module ClassConfigured
28
+ def authorizes?(context)
29
+ method(:authorized?).owner != GraphQL::Schema::Member::HasAuthorization
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -22,6 +22,14 @@ module GraphQL
22
22
 
23
23
  # A shortcut method for loading many keys from a source.
24
24
  # Identical to `dataloader.with(source_class, *source_args).load_all(load_keys)`
25
+ #
26
+ # @example
27
+ # field :score, Integer, resolve_batch: true
28
+ #
29
+ # def self.score(posts)
30
+ # dataload_all(PostScoreSource, posts.map(&:id))
31
+ # end
32
+ #
25
33
  # @param source_class [Class<GraphQL::Dataloader::Source>]
26
34
  # @param source_args [Array<Object>] Any extra parameters defined in `source_class`'s `initialize` method
27
35
  # @param load_keys [Array<Object>] The keys to look up using `def fetch`
@@ -48,6 +56,16 @@ module GraphQL
48
56
  source.load(find_by_value)
49
57
  end
50
58
 
59
+ # @see dataload_record Like `dataload_record`, but accepts an Array of `find_by_values`
60
+ def dataload_all_records(model, find_by_values, find_by: nil)
61
+ source = if find_by
62
+ dataloader.with(Dataloader::ActiveRecordSource, model, find_by: find_by)
63
+ else
64
+ dataloader.with(Dataloader::ActiveRecordSource, model)
65
+ end
66
+ source.load_all(find_by_values)
67
+ end
68
+
51
69
  # Look up an associated record using a Rails association (via {Dataloader::ActiveRecordAssociationSource})
52
70
  # @param association_name [Symbol] A `belongs_to` or `has_one` association. (If a `has_many` association is named here, it will be selected without pagination.)
53
71
  # @param record [ActiveRecord::Base] The object that the association belongs to.
@@ -65,6 +83,16 @@ module GraphQL
65
83
  end
66
84
  source.load(record)
67
85
  end
86
+
87
+ # @see dataload_association Like `dataload_assocation` but accepts an Array of records (required param)
88
+ def dataload_all_associations(records, association_name, scope: nil)
89
+ source = if scope
90
+ dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name, scope)
91
+ else
92
+ dataloader.with(Dataloader::ActiveRecordAssociationSource, association_name)
93
+ end
94
+ source.load_all(records)
95
+ end
68
96
  end
69
97
  end
70
98
  end
@@ -19,10 +19,11 @@ module GraphQL
19
19
  # @option kwargs [Symbol] :method The method to call on the underlying object to resolve this field (defaults to `name`)
20
20
  # @option kwargs [String, Symbol] :hash_key The hash key to lookup on the underlying object (if its a Hash) to resolve this field (defaults to `name` or `name.to_s`)
21
21
  # @option kwargs [Array<String, Symbol>] :dig The nested hash keys to lookup on the underlying hash to resolve this field using dig
22
- # @option kwargs [Symbol] :resolver_method The method on the type to call to resolve this field (defaults to `name`)
23
- # @option kwargs [Symbol] :resolve_static Used by {Schema.execute_batching} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
24
- # @option kwargs [Symbol] :resolve_batch Used by {Schema.execute_batching} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
25
- # @option kwargs [Symbol] :resolve_each Used by {Schema.execute_batching} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
22
+ # @option kwargs [Symbol, true] :resolver_method The method on the type to call to resolve this field (defaults to `name`)
23
+ # @option kwargs [Symbol, true] :resolve_static Used by {Schema.execute_next} to produce a single value, shared by all objects which resolve this field. Called on the owner type class with `context, **arguments`
24
+ # @option kwargs [Symbol, true] :resolve_batch Used by {Schema.execute_next} map `objects` to a same-sized Array of results. Called on the owner type class with `objects, context, **arguments`.
25
+ # @option kwargs [Symbol, true] :resolve_each Used by {Schema.execute_next} to get a value value for each item. Called on the owner type class with `object, context, **arguments`.
26
+ # @option kwargs [Symbol, true] :resolve_legacy_instance_method Used by {Schema.execute_next} to get a value value for each item. Calls an instance method on the object type class.
26
27
  # @option kwargs [Boolean] :connection `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name
27
28
  # @option kwargs [Class] :connection_extension The extension to add, to implement connections. If `nil`, no extension is added.
28
29
  # @option kwargs [Integer, nil] :max_page_size For connections, the maximum number of items to return from this field, or `nil` to allow unlimited results.
@@ -47,6 +48,7 @@ module GraphQL
47
48
  # @option kwargs [Boolean] :dynamic_introspection (Private, used by GraphQL-Ruby)
48
49
  # @option kwargs [Boolean] :relay_node_field (Private, used by GraphQL-Ruby)
49
50
  # @option kwargs [Boolean] :relay_nodes_field (Private, used by GraphQL-Ruby)
51
+ # @option kwargs [Class, Hash] :dataload Shorthand for dataloader lookups
50
52
  # @option kwargs [Array<:ast_node, :parent, :lookahead, :owner, :execution_errors, :graphql_name, :argument_details, Symbol>] :extras Extra arguments to be injected into the resolver for this field
51
53
  # @param kwargs [Hash] Keywords for defining the field. Any not documented here will be passed to your base field class where they must be handled.
52
54
  # @param definition_block [Proc] an additional block for configuring the field. Receive the field as a block param, or, if no block params are defined, then the block is `instance_eval`'d on the new {Field}.
@@ -148,10 +150,14 @@ module GraphQL
148
150
 
149
151
  def global_id_field(field_name, **kwargs)
150
152
  type = self
151
- field field_name, "ID", **kwargs, null: false
153
+ field field_name, "ID", **kwargs, null: false, resolve_each: true
152
154
  define_method(field_name) do
153
155
  context.schema.id_from_object(object, type, context)
154
156
  end
157
+
158
+ define_singleton_method(field_name) do |object, context|
159
+ context.schema.id_from_object(object, type, context)
160
+ end
155
161
  end
156
162
 
157
163
  # @param new_has_no_fields [Boolean] Call with `true` to make this Object type ignore the requirement to have any defined fields.
@@ -2,6 +2,7 @@
2
2
  require 'graphql/schema/member/base_dsl_methods'
3
3
  require 'graphql/schema/member/graphql_type_names'
4
4
  require 'graphql/schema/member/has_ast_node'
5
+ require 'graphql/schema/member/has_authorization'
5
6
  require 'graphql/schema/member/has_dataloader'
6
7
  require 'graphql/schema/member/has_directives'
7
8
  require 'graphql/schema/member/has_deprecation_reason'
@@ -31,6 +32,10 @@ module GraphQL
31
32
  extend HasPath
32
33
  extend HasAstNode
33
34
  extend HasDirectives
35
+
36
+ def self.authorizes?(_ctx)
37
+ false
38
+ end
34
39
  end
35
40
  end
36
41
  end
@@ -24,7 +24,7 @@ module GraphQL
24
24
  end
25
25
 
26
26
  def to_type_signature
27
- "#{@of_type.to_type_signature}!"
27
+ @type_signature ||= -"#{@of_type.to_type_signature}!"
28
28
  end
29
29
 
30
30
  def inspect
@@ -5,6 +5,7 @@ require "graphql/query/null_context"
5
5
  module GraphQL
6
6
  class Schema
7
7
  class Object < GraphQL::Schema::Member
8
+ extend GraphQL::Schema::Member::HasAuthorization
8
9
  extend GraphQL::Schema::Member::HasFields
9
10
  extend GraphQL::Schema::Member::HasInterfaces
10
11
  include Member::HasDataloader
@@ -23,6 +23,7 @@ module GraphQL
23
23
  # Really we only need description & comment from here, but:
24
24
  extend Schema::Member::BaseDSLMethods
25
25
  extend GraphQL::Schema::Member::HasArguments
26
+ extend GraphQL::Schema::Member::HasAuthorization
26
27
  extend GraphQL::Schema::Member::HasValidators
27
28
  include Schema::Member::HasPath
28
29
  extend Schema::Member::HasPath
@@ -45,6 +46,8 @@ module GraphQL
45
46
  @prepared_arguments = nil
46
47
  end
47
48
 
49
+ attr_accessor :exec_result, :exec_index, :field_resolve_step, :raw_arguments
50
+
48
51
  # @return [Object] The application object this field is being resolved on
49
52
  attr_accessor :object
50
53
 
@@ -56,6 +59,79 @@ module GraphQL
56
59
 
57
60
  attr_writer :prepared_arguments
58
61
 
62
+ def call
63
+ if self.class < Schema::HasSingleInputArgument
64
+ @prepared_arguments = @prepared_arguments[:input]
65
+ end
66
+ q = context.query
67
+ trace_objs = [object]
68
+ q.current_trace.begin_execute_field(field, @prepared_arguments, trace_objs, q)
69
+ is_ready = ready?(**@prepared_arguments)
70
+ runner = @field_resolve_step.runner
71
+ if runner.resolves_lazies && runner.schema.lazy?(is_ready)
72
+ is_ready, new_return_value = runner.schema.sync_lazy(is_ready)
73
+ end
74
+
75
+ if is_ready.is_a?(Array)
76
+ is_ready, new_return_value = is_ready
77
+ if is_ready != false
78
+ raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{is_ready.inspect}, #{new_return_value.inspect}]"
79
+ else
80
+ new_return_value
81
+ end
82
+ end
83
+
84
+ if is_ready
85
+ begin
86
+ is_authed, new_return_value = authorized?(**@prepared_arguments)
87
+ rescue GraphQL::UnauthorizedError => err
88
+ new_return_value = q.schema.unauthorized_object(err)
89
+ is_authed = true # the error was handled
90
+ end
91
+ end
92
+
93
+ if runner.resolves_lazies && runner.schema.lazy?(is_authed)
94
+ is_authed, new_return_value = runner.schema.sync_lazy(is_authed)
95
+ end
96
+
97
+ result = if is_authed
98
+ Schema::Validator.validate!(self.class.validators, object, context, @prepared_arguments, as: @field)
99
+ if q.subscription? && @field.owner == context.schema.subscription
100
+ # This needs to use arguments without `loads:`. TODO extract this into subscription-related code somehow?
101
+ @original_arguments = @field_resolve_step.runner.input_values[q].argument_values(@field, @field_resolve_step.ast_node.arguments, nil)
102
+ end
103
+ call_resolve(@prepared_arguments)
104
+ elsif new_return_value.nil?
105
+ err = UnauthorizedFieldError.new(object: object, type: @field_resolve_step.parent_type, context: context, field: @field)
106
+ context.schema.unauthorized_field(err)
107
+ else
108
+ new_return_value
109
+ end
110
+ q = context.query
111
+ q.current_trace.end_execute_field(field, @prepared_arguments, trace_objs, q, [result])
112
+ exec_result[exec_index] = result
113
+ rescue GraphQL::UnauthorizedError => auth_err
114
+ exec_result[exec_index] = begin
115
+ context.schema.unauthorized_object(auth_err)
116
+ rescue GraphQL::ExecutionError => exec_err
117
+ exec_err
118
+ end
119
+ rescue GraphQL::RuntimeError => err
120
+ exec_result[exec_index] = err
121
+ rescue StandardError => stderr
122
+ exec_result[exec_index] = begin
123
+ context.query.handle_or_reraise(stderr)
124
+ rescue GraphQL::ExecutionError => ex_err
125
+ ex_err
126
+ end
127
+ ensure
128
+ field_pending_steps = field_resolve_step.pending_steps
129
+ field_pending_steps.delete(self)
130
+ if field_pending_steps.size == 0 && field_resolve_step.field_results
131
+ field_resolve_step.runner.add_step(field_resolve_step)
132
+ end
133
+ end
134
+
59
135
  def arguments
60
136
  @prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
61
137
  end
@@ -156,6 +232,10 @@ module GraphQL
156
232
  authorize_arguments(args, inputs)
157
233
  end
158
234
 
235
+ def self.authorizes?(context)
236
+ self.instance_method(:authorized?).owner != GraphQL::Schema::Resolver
237
+ end
238
+
159
239
  # Called when an object loaded by `loads:` fails the `.authorized?` check for its resolved GraphQL object type.
160
240
  #
161
241
  # By default, the error is re-raised and passed along to {{Schema.unauthorized_object}}.
@@ -15,8 +15,6 @@ module GraphQL
15
15
  extend GraphQL::Schema::Resolver::HasPayloadType
16
16
  extend GraphQL::Schema::Member::HasFields
17
17
  NO_UPDATE = :no_update
18
- # The generated payload type is required; If there's no payload,
19
- # propagate null.
20
18
  null false
21
19
 
22
20
  # @api private