graphql 2.5.11 → 2.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/field_extractor.rb +17 -1
  4. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  5. data/lib/generators/graphql/templates/schema.erb +2 -1
  6. data/lib/graphql/analysis/query_complexity.rb +31 -15
  7. data/lib/graphql/analysis.rb +20 -13
  8. data/lib/graphql/backtrace/table.rb +10 -1
  9. data/lib/graphql/current.rb +7 -1
  10. data/lib/graphql/dashboard/application_controller.rb +45 -0
  11. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  12. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  13. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  15. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  16. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  17. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  18. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  19. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  20. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  21. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  22. data/lib/graphql/dashboard.rb +14 -73
  23. data/lib/graphql/dataloader/async_dataloader.rb +336 -60
  24. data/lib/graphql/dataloader/null_dataloader.rb +48 -10
  25. data/lib/graphql/dataloader/source.rb +32 -19
  26. data/lib/graphql/dataloader.rb +107 -39
  27. data/lib/graphql/date_encoding_error.rb +1 -1
  28. data/lib/graphql/execution/directive_checks.rb +2 -0
  29. data/lib/graphql/execution/field_resolve_step.rb +804 -0
  30. data/lib/graphql/execution/finalize.rb +229 -0
  31. data/lib/graphql/execution/input_values.rb +333 -0
  32. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
  33. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  34. data/lib/graphql/execution/interpreter/resolve.rb +7 -13
  35. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  36. data/lib/graphql/execution/interpreter/runtime.rb +60 -40
  37. data/lib/graphql/execution/interpreter.rb +17 -23
  38. data/lib/graphql/execution/lazy.rb +1 -1
  39. data/lib/graphql/execution/load_argument_step.rb +102 -0
  40. data/lib/graphql/execution/multiplex.rb +1 -1
  41. data/lib/graphql/execution/next.rb +98 -0
  42. data/lib/graphql/execution/prepare_object_step.rb +151 -0
  43. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  44. data/lib/graphql/execution/runner.rb +457 -0
  45. data/lib/graphql/execution/selections_step.rb +95 -0
  46. data/lib/graphql/execution.rb +15 -4
  47. data/lib/graphql/execution_error.rb +17 -12
  48. data/lib/graphql/float_decoding_error.rb +13 -0
  49. data/lib/graphql/float_encoding_error.rb +28 -0
  50. data/lib/graphql/introspection/directive_type.rb +7 -3
  51. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  52. data/lib/graphql/introspection/entry_points.rb +11 -3
  53. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  54. data/lib/graphql/introspection/field_type.rb +13 -5
  55. data/lib/graphql/introspection/input_value_type.rb +21 -13
  56. data/lib/graphql/introspection/schema_type.rb +6 -2
  57. data/lib/graphql/introspection/type_type.rb +64 -28
  58. data/lib/graphql/invalid_null_error.rb +11 -5
  59. data/lib/graphql/language/block_string.rb +6 -11
  60. data/lib/graphql/language/cache.rb +84 -14
  61. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  62. data/lib/graphql/language/lexer.rb +27 -17
  63. data/lib/graphql/language/nodes.rb +2 -1
  64. data/lib/graphql/language/parser.rb +1 -1
  65. data/lib/graphql/language.rb +32 -17
  66. data/lib/graphql/pagination/connection.rb +2 -0
  67. data/lib/graphql/pagination/connections.rb +30 -0
  68. data/lib/graphql/pagination/relation_connection.rb +10 -1
  69. data/lib/graphql/query/context.rb +10 -3
  70. data/lib/graphql/query/null_context.rb +9 -3
  71. data/lib/graphql/query/partial.rb +18 -3
  72. data/lib/graphql/query/variable_validation_error.rb +16 -1
  73. data/lib/graphql/query/variables.rb +1 -1
  74. data/lib/graphql/query.rb +12 -3
  75. data/lib/graphql/railtie.rb +2 -1
  76. data/lib/graphql/runtime_error.rb +6 -0
  77. data/lib/graphql/schema/argument.rb +13 -1
  78. data/lib/graphql/schema/build_from_definition.rb +28 -1
  79. data/lib/graphql/schema/directive/feature.rb +4 -0
  80. data/lib/graphql/schema/directive/transform.rb +20 -0
  81. data/lib/graphql/schema/directive.rb +48 -13
  82. data/lib/graphql/schema/field/connection_extension.rb +4 -37
  83. data/lib/graphql/schema/field/scope_extension.rb +18 -13
  84. data/lib/graphql/schema/field.rb +79 -48
  85. data/lib/graphql/schema/field_extension.rb +11 -8
  86. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  87. data/lib/graphql/schema/input_object.rb +6 -2
  88. data/lib/graphql/schema/interface.rb +26 -0
  89. data/lib/graphql/schema/introspection_system.rb +6 -21
  90. data/lib/graphql/schema/list.rb +5 -1
  91. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
  92. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  93. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  94. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  95. data/lib/graphql/schema/member/has_fields.rb +86 -5
  96. data/lib/graphql/schema/member.rb +5 -0
  97. data/lib/graphql/schema/non_null.rb +1 -1
  98. data/lib/graphql/schema/object.rb +1 -0
  99. data/lib/graphql/schema/printer.rb +1 -1
  100. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  101. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  102. data/lib/graphql/schema/resolver.rb +76 -1
  103. data/lib/graphql/schema/subscription.rb +53 -10
  104. data/lib/graphql/schema/timeout.rb +2 -2
  105. data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
  106. data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
  107. data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
  108. data/lib/graphql/schema/validator/format_validator.rb +3 -3
  109. data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
  110. data/lib/graphql/schema/validator/length_validator.rb +6 -6
  111. data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
  112. data/lib/graphql/schema/validator/required_validator.rb +38 -5
  113. data/lib/graphql/schema/validator.rb +9 -0
  114. data/lib/graphql/schema/visibility/profile.rb +74 -53
  115. data/lib/graphql/schema/visibility/visit.rb +1 -1
  116. data/lib/graphql/schema/visibility.rb +32 -24
  117. data/lib/graphql/schema/wrapper.rb +11 -1
  118. data/lib/graphql/schema.rb +93 -27
  119. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  120. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  121. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  122. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  123. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  124. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  125. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
  126. data/lib/graphql/static_validation/rules/fields_will_merge.rb +351 -248
  127. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  128. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  129. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  130. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  131. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  132. data/lib/graphql/static_validation/validation_context.rb +1 -1
  133. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  134. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +40 -10
  135. data/lib/graphql/subscriptions/serialize.rb +10 -4
  136. data/lib/graphql/subscriptions.rb +36 -1
  137. data/lib/graphql/testing/helpers.rb +12 -9
  138. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  139. data/lib/graphql/testing.rb +1 -0
  140. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  141. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  142. data/lib/graphql/tracing/perfetto_trace.rb +213 -81
  143. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  144. data/lib/graphql/tracing/trace.rb +6 -0
  145. data/lib/graphql/types/float.rb +18 -4
  146. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  147. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  148. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  149. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  150. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  151. data/lib/graphql/unauthorized_error.rb +9 -1
  152. data/lib/graphql/version.rb +1 -1
  153. data/lib/graphql.rb +8 -2
  154. metadata +23 -59
@@ -4,10 +4,30 @@ module GraphQL
4
4
  class Schema
5
5
  module HasSingleInputArgument
6
6
  def resolve_with_support(**inputs)
7
- if inputs[:input].is_a?(InputObject)
8
- input = inputs[:input].to_kwargs
7
+ input_kwargs = flatten_arguments(inputs)
8
+ if !input_kwargs.empty?
9
+ super(**input_kwargs)
10
+ else
11
+ super()
12
+ end
13
+ end
14
+
15
+ def self.included(base)
16
+ base.extend(ClassMethods)
17
+ end
18
+
19
+ def call
20
+ @prepared_arguments = flatten_arguments(@prepared_arguments)
21
+ super
22
+ end
23
+
24
+ private
25
+
26
+ def flatten_arguments(inputs)
27
+ input = if inputs[:input].is_a?(InputObject)
28
+ inputs[:input].to_kwargs
9
29
  else
10
- input = inputs[:input]
30
+ inputs[:input]
11
31
  end
12
32
 
13
33
  new_extras = field ? field.extras : []
@@ -24,7 +44,6 @@ module GraphQL
24
44
  end
25
45
 
26
46
  if input
27
- # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
28
47
  input_kwargs = input.to_h
29
48
  else
30
49
  # Relay Classic Mutations with no `argument`s
@@ -32,15 +51,7 @@ module GraphQL
32
51
  input_kwargs = {}
33
52
  end
34
53
 
35
- if !input_kwargs.empty?
36
- super(**input_kwargs)
37
- else
38
- super()
39
- end
40
- end
41
-
42
- def self.included(base)
43
- base.extend(ClassMethods)
54
+ input_kwargs
44
55
  end
45
56
 
46
57
  module ClassMethods
@@ -113,6 +113,10 @@ module GraphQL
113
113
  end
114
114
 
115
115
  class << self
116
+ def authorizes?(ctx)
117
+ self.method(:authorized?).owner != GraphQL::Schema::InputObject
118
+ end
119
+
116
120
  def authorized?(obj, value, ctx)
117
121
  # Authorize each argument (but this doesn't apply if `prepare` is implemented):
118
122
  if value.respond_to?(:key?)
@@ -168,12 +172,12 @@ module GraphQL
168
172
  types = ctx.types
169
173
 
170
174
  if input.is_a?(Array)
171
- return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
175
+ return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input) })
172
176
  end
173
177
 
174
178
  if !(input.respond_to?(:to_h) || input.respond_to?(:to_unsafe_h))
175
179
  # We're not sure it'll act like a hash, so reject it:
176
- return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input, quirks_mode: true) })
180
+ return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input) })
177
181
  end
178
182
 
179
183
 
@@ -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 != child_class && ancestor <= GraphQL::Schema::Interface && ancestor.const_defined?(:ResolverMethods, false)
105
+ child_class.extend(ancestor::ResolverMethods)
106
+ end
107
+ end
82
108
  end
83
109
 
84
110
  super
@@ -105,11 +105,13 @@ module GraphQL
105
105
  end
106
106
 
107
107
  def load_constant(class_name)
108
- const = @custom_namespace.const_get(class_name)
108
+ const = begin
109
+ @custom_namespace.const_get(class_name)
110
+ rescue NameError
111
+ # Dup the built-in so that the cached fields aren't shared
112
+ @built_in_namespace.const_get(class_name)
113
+ end
109
114
  dup_type_class(const)
110
- rescue NameError
111
- # Dup the built-in so that the cached fields aren't shared
112
- dup_type_class(@built_in_namespace.const_get(class_name))
113
115
  end
114
116
 
115
117
  def get_fields_from_class(class_sym:)
@@ -133,23 +135,6 @@ module GraphQL
133
135
  end
134
136
  end
135
137
  end
136
-
137
- class PerFieldProxyResolve
138
- def initialize(object_class:, inner_resolve:)
139
- @object_class = object_class
140
- @inner_resolve = inner_resolve
141
- end
142
-
143
- def call(obj, args, ctx)
144
- query_ctx = ctx.query.context
145
- # Remove the QueryType wrapper
146
- if obj.is_a?(GraphQL::Schema::Object)
147
- obj = obj.object
148
- end
149
- wrapped_object = @object_class.wrap(obj, query_ctx)
150
- @inner_resolve.call(wrapped_object, args, ctx)
151
- end
152
- end
153
138
  end
154
139
  end
155
140
  end
@@ -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
 
@@ -14,29 +14,52 @@ module GraphQL
14
14
  cls.extend(ClassConfigured)
15
15
  end
16
16
 
17
- # @see {GraphQL::Schema::Argument#initialize} for parameters
18
- # @return [GraphQL::Schema::Argument] An instance of {argument_class}, created from `*args`
19
- def argument(*args, **kwargs, &block)
20
- kwargs[:owner] = self
21
- loads = kwargs[:loads]
22
- if loads
23
- name = args[0]
24
- name_as_string = name.to_s
25
-
26
- inferred_arg_name = case name_as_string
17
+ # @param arg_name [Symbol] The underscore-cased name of this argument, `name:` keyword also accepted
18
+ # @param type_expr The GraphQL type of this argument; `type:` keyword also accepted
19
+ # @param desc [String] Argument description, `description:` keyword also accepted
20
+ # @option kwargs [Boolean, :nullable] :required if true, this argument is non-null; if false, this argument is nullable. If `:nullable`, then the argument must be provided, though it may be `null`.
21
+ # @option kwargs [String] :description Positional argument also accepted
22
+ # @option kwargs [Class, Array<Class>] :type Input type; positional argument also accepted
23
+ # @option kwargs [Symbol] :name positional argument also accepted
24
+ # @option kwargs [Object] :default_value
25
+ # @option kwargs [Class, Array<Class>] :loads A GraphQL type to load for the given ID when one is present
26
+ # @option kwargs [Symbol] :as Override the keyword name when passed to a method
27
+ # @option kwargs [Symbol] :prepare A method to call to transform this argument's valuebefore sending it to field resolution
28
+ # @option kwargs [Boolean] :camelize if true, the name will be camelized when building the schema
29
+ # @option kwargs [Boolean] :from_resolver if true, a Resolver class defined this argument
30
+ # @option kwargs [Hash{Class => Hash}] :directives
31
+ # @option kwargs [String] :deprecation_reason
32
+ # @option kwargs [String] :comment Private, used by GraphQL-Ruby when parsing GraphQL schema files
33
+ # @option kwargs [GraphQL::Language::Nodes::InputValueDefinition] :ast_node Private, used by GraphQL-Ruby when parsing schema files
34
+ # @option kwargs [Hash, nil] :validates Options for building validators, if any should be applied
35
+ # @option kwargs [Boolean] :replace_null_with_default if `true`, incoming values of `null` will be replaced with the configured `default_value`
36
+ # @param definition_block [Proc] Called with the newly-created {Argument}
37
+ # @param kwargs [Hash] Keywords for defining an argument. Any keywords not documented here must be handled by your base Argument class.
38
+ # @return [GraphQL::Schema::Argument] An instance of {argument_class} created from these arguments
39
+ def argument(arg_name = nil, type_expr = nil, desc = nil, **kwargs, &definition_block)
40
+ if kwargs[:loads]
41
+ loads_name = arg_name || kwargs[:name]
42
+ loads_name_as_string = loads_name.to_s
43
+
44
+ inferred_arg_name = case loads_name_as_string
27
45
  when /_id$/
28
- name_as_string.sub(/_id$/, "").to_sym
46
+ loads_name_as_string.sub(/_id$/, "").to_sym
29
47
  when /_ids$/
30
- name_as_string.sub(/_ids$/, "")
48
+ loads_name_as_string.sub(/_ids$/, "")
31
49
  .sub(/([^s])$/, "\\1s")
32
50
  .to_sym
33
51
  else
34
- name
52
+ loads_name
35
53
  end
36
54
 
37
55
  kwargs[:as] ||= inferred_arg_name
38
56
  end
39
- arg_defn = self.argument_class.new(*args, **kwargs, &block)
57
+ kwargs[:owner] = self
58
+ arg_defn = self.argument_class.new(
59
+ arg_name, type_expr, desc,
60
+ **kwargs,
61
+ &definition_block
62
+ )
40
63
  add_argument(arg_defn)
41
64
  arg_defn
42
65
  end
@@ -413,6 +436,12 @@ module GraphQL
413
436
  end
414
437
  end
415
438
 
439
+ # Called when an argument's `loads:` configuration fails to fetch an application object.
440
+ # By default, this method raises the given error, but you can override it to handle failures differently.
441
+ #
442
+ # @param err [GraphQL::LoadApplicationObjectFailedError] The error that occurred
443
+ # @return [Object, nil] If a value is returned, it will be used instead of the failed load
444
+ # @api public
416
445
  def load_application_object_failed(err)
417
446
  raise err
418
447
  end
@@ -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
@@ -20,6 +20,23 @@ module GraphQL
20
20
  dataloader.with(source_class, *source_args).load(load_key)
21
21
  end
22
22
 
23
+ # A shortcut method for loading many keys from a source.
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
+ #
33
+ # @param source_class [Class<GraphQL::Dataloader::Source>]
34
+ # @param source_args [Array<Object>] Any extra parameters defined in `source_class`'s `initialize` method
35
+ # @param load_keys [Array<Object>] The keys to look up using `def fetch`
36
+ def dataload_all(source_class, *source_args, load_keys)
37
+ dataloader.with(source_class, *source_args).load_all(load_keys)
38
+ end
39
+
23
40
  # Find an object with ActiveRecord via {Dataloader::ActiveRecordSource}.
24
41
  # @param model [Class<ActiveRecord::Base>]
25
42
  # @param find_by_value [Object] Usually an `id`, might be another value if `find_by:` is also provided
@@ -39,6 +56,16 @@ module GraphQL
39
56
  source.load(find_by_value)
40
57
  end
41
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
+
42
69
  # Look up an associated record using a Rails association (via {Dataloader::ActiveRecordAssociationSource})
43
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.)
44
71
  # @param record [ActiveRecord::Base] The object that the association belongs to.
@@ -56,6 +83,16 @@ module GraphQL
56
83
  end
57
84
  source.load(record)
58
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
59
96
  end
60
97
  end
61
98
  end
@@ -5,11 +5,88 @@ module GraphQL
5
5
  class Member
6
6
  # Shared code for Objects, Interfaces, Mutations, Subscriptions
7
7
  module HasFields
8
+ include EmptyObjects
8
9
  # Add a field to this object or interface with the given definition
9
- # @see {GraphQL::Schema::Field#initialize} for method signature
10
+ # @param name_positional [Symbol] The underscore-cased version of this field name (will be camelized for the GraphQL API); `name:` keyword is also accepted
11
+ # @param type_positional [Class, GraphQL::BaseType, Array] The return type of this field; `type:` keyword is also accepted
12
+ # @param desc_positional [String] Field description; `description:` keyword is also accepted
13
+ # @option kwargs [Symbol] :name The underscore-cased version of this field name (will be camelized for the GraphQL API); positional argument also accepted
14
+ # @option kwargs [Class, GraphQL::BaseType, Array] :type The return type of this field; positional argument is also accepted
15
+ # @option kwargs [Boolean] :null (defaults to `true`) `true` if this field may return `null`, `false` if it is never `null`
16
+ # @option kwargs [String] :description Field description; positional argument also accepted
17
+ # @option kwargs [String] :comment Field comment
18
+ # @option kwargs [String] :deprecation_reason If present, the field is marked "deprecated" with this message
19
+ # @option kwargs [Symbol] :method The method to call on the underlying object to resolve this field (defaults to `name`)
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
+ # @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, 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.
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
28
+ # @option kwargs [Class] :connection_extension The extension to add, to implement connections. If `nil`, no extension is added.
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.
30
+ # @option kwargs [Integer, nil] :default_page_size For connections, the default number of items to return from this field, or `nil` to return unlimited results.
31
+ # @option kwargs [Boolean] :introspection If true, this field will be marked as `#introspection?` and the name may begin with `__`
32
+ # @option kwargs [{String=>GraphQL::Schema::Argument, Hash}] :arguments Arguments for this field (may be added in the block, also)
33
+ # @option kwargs [Boolean] :camelize If true, the field name will be camelized when building the schema
34
+ # @option kwargs [Numeric] :complexity When provided, set the complexity for this field
35
+ # @option kwargs [Boolean] :scope If true, the return type's `.scope_items` method will be called on the return value
36
+ # @option kwargs [Symbol, String] :subscription_scope A key in `context` which will be used to scope subscription payloads
37
+ # @option kwargs [Array<Class, Hash<Class => Object>>] :extensions Named extensions to apply to this field (see also {#extension})
38
+ # @option kwargs [Hash{Class => Hash}] :directives Directives to apply to this field
39
+ # @option kwargs [Boolean] :trace If true, a {GraphQL::Tracing} tracer will measure this scalar field
40
+ # @option kwargs [Boolean] :broadcastable Whether or not this field can be distributed in subscription broadcasts
41
+ # @option kwargs [Language::Nodes::FieldDefinition, nil] :ast_node If this schema was parsed from definition, this AST node defined the field
42
+ # @option kwargs [Boolean] :method_conflict_warning If false, skip the warning if this field's method conflicts with a built-in method
43
+ # @option kwargs [Array<Hash>] :validates Configurations for validating this field
44
+ # @option kwargs [Object] :fallback_value A fallback value if the method is not defined
45
+ # @option kwargs [Class<GraphQL::Schema::Mutation>] :mutation
46
+ # @option kwargs [Class<GraphQL::Schema::Resolver>] :resolver
47
+ # @option kwargs [Class<GraphQL::Schema::Subscription>] :subscription
48
+ # @option kwargs [Boolean] :dynamic_introspection (Private, used by GraphQL-Ruby)
49
+ # @option kwargs [Boolean] :relay_node_field (Private, used by GraphQL-Ruby)
50
+ # @option kwargs [Boolean] :relay_nodes_field (Private, used by GraphQL-Ruby)
51
+ # @option kwargs [Class, Hash] :dataload Shorthand for dataloader lookups
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
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.
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}.
55
+ # @yieldparam field [GraphQL::Schema::Field] The newly-created field instance
56
+ # @yieldreturn [void]
10
57
  # @return [GraphQL::Schema::Field]
11
- def field(*args, **kwargs, &block)
12
- field_defn = field_class.from_options(*args, owner: self, **kwargs, &block)
58
+ def field(name_positional = nil, type_positional = nil, desc_positional = nil, **kwargs, &definition_block)
59
+ resolver = kwargs.delete(:resolver)
60
+ mutation = kwargs.delete(:mutation)
61
+ subscription = kwargs.delete(:subscription)
62
+ if (resolver_class = resolver || mutation || subscription)
63
+ # Add a reference to that parent class
64
+ kwargs[:resolver_class] = resolver_class
65
+ end
66
+
67
+ kwargs[:name] ||= name_positional
68
+ if !type_positional.nil?
69
+ if desc_positional
70
+ if kwargs[:description]
71
+ raise ArgumentError, "Provide description as a positional argument or `description:` keyword, but not both (#{desc_positional.inspect}, #{kwargs[:description].inspect})"
72
+ end
73
+
74
+ kwargs[:description] = desc_positional
75
+ kwargs[:type] = type_positional
76
+ elsif (resolver || mutation) && type_positional.is_a?(String)
77
+ # The return type should be copied from the resolver, and the second positional argument is the description
78
+ kwargs[:description] = type_positional
79
+ else
80
+ kwargs[:type] = type_positional
81
+ end
82
+
83
+ if type_positional.is_a?(Class) && type_positional < GraphQL::Schema::Mutation
84
+ raise ArgumentError, "Use `field #{name_positional.inspect}, mutation: Mutation, ...` to provide a mutation to this field instead"
85
+ end
86
+ end
87
+
88
+ kwargs[:owner] = self
89
+ field_defn = field_class.new(**kwargs, &definition_block)
13
90
  add_field(field_defn)
14
91
  field_defn
15
92
  end
@@ -73,10 +150,14 @@ module GraphQL
73
150
 
74
151
  def global_id_field(field_name, **kwargs)
75
152
  type = self
76
- field field_name, "ID", **kwargs, null: false
153
+ field field_name, "ID", **kwargs, null: false, resolve_each: true
77
154
  define_method(field_name) do
78
155
  context.schema.id_from_object(object, type, context)
79
156
  end
157
+
158
+ define_singleton_method(field_name) do |object, context|
159
+ context.schema.id_from_object(object, type, context)
160
+ end
80
161
  end
81
162
 
82
163
  # @param new_has_no_fields [Boolean] Call with `true` to make this Object type ignore the requirement to have any defined fields.
@@ -232,7 +313,7 @@ module GraphQL
232
313
  end
233
314
  end
234
315
 
235
- # @param [GraphQL::Schema::Field]
316
+ # @param field_defn [GraphQL::Schema::Field]
236
317
  # @return [String] A warning to give when this field definition might conflict with a built-in method
237
318
  def conflict_field_name_warning(field_defn)
238
319
  "#{self.graphql_name}'s `field :#{field_defn.original_name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.resolver_method}` and `def resolve_#{field_defn.resolver_method}`). Or use `method_conflict_warning: false` to suppress this warning."
@@ -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
@@ -58,8 +58,8 @@ module GraphQL
58
58
  end
59
59
  end
60
60
  schema = Class.new(GraphQL::Schema) {
61
- use GraphQL::Schema::Visibility
62
61
  query(query_root)
62
+ use GraphQL::Schema::Visibility
63
63
  def self.visible?(member, _ctx)
64
64
  member.graphql_name != "Root"
65
65
  end
@@ -72,6 +72,7 @@ module GraphQL
72
72
  # How to support it?
73
73
  def lazy?(_obj); false; end
74
74
  def sync_lazy(obj); obj; end
75
+ def resolves_lazies?; false; end
75
76
  end
76
77
  end
77
78
  end
@@ -25,7 +25,7 @@ module GraphQL
25
25
  argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
26
26
 
27
27
  # The payload should always include this field
28
- field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.")
28
+ field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.", hash_key: :client_mutation_id)
29
29
  # Relay classic default:
30
30
  null(true)
31
31
 
@@ -35,7 +35,6 @@ module GraphQL
35
35
  input = inputs[:input].to_kwargs
36
36
 
37
37
  if input
38
- # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
39
38
  input_kwargs = input.to_h
40
39
  client_mutation_id = input_kwargs.delete(:client_mutation_id)
41
40
  inputs[:input] = input_kwargs
@@ -51,6 +50,21 @@ module GraphQL
51
50
  return_hash
52
51
  end
53
52
  end
53
+
54
+ def call
55
+ input = @prepared_arguments[:input]&.to_kwargs
56
+
57
+ if input
58
+ client_mutation_id = input.delete(:client_mutation_id)
59
+ @prepared_arguments[:input] = input
60
+ end
61
+
62
+ super
63
+
64
+ if (return_value = exec_result[exec_index]).is_a?(Hash)
65
+ return_value[:client_mutation_id] = client_mutation_id
66
+ end
67
+ end
54
68
  end
55
69
  end
56
70
  end