Class: GraphQL::Schema::Resolver

Inherits:
Object
  • Object
show all
Extended by:
Member::BaseDSLMethods, Member::HasArguments, Member::HasPath, Member::HasValidators
Includes:
Member::GraphQLTypeNames, Member::HasPath
Defined in:
lib/graphql/schema/resolver.rb,
lib/graphql/schema/resolver/has_payload_type.rb

Overview

A class-based container for field configuration and resolution logic. It supports:

  • Arguments, via .argument(...) helper, which will be applied to the field.
  • Return type, via .type(..., null: ...), which will be applied to the field.
  • Description, via .description(...), which will be applied to the field
  • Resolution, via #resolve(**args) method, which will be called to resolve the field.
  • #object and #context accessors for use during #resolve.

Resolvers can be attached with the resolver: option in a field(...) call.

A resolver's configuration may be overridden with other keywords in the field(...) call.

See the Resolver.field_options to see how a Resolver becomes a set of field configuration options.

See Also:

  • for a concrete subclass of `Resolver`.
  • `Resolver` is a replacement for `GraphQL::Function`

Direct Known Subclasses

Mutation, Subscription

Defined Under Namespace

Modules: HasPayloadType

Constant Summary

Constants included from Member::HasArguments

Member::HasArguments::NO_ARGUMENTS

Constants included from FindInheritedValue::EmptyObjects

FindInheritedValue::EmptyObjects::EMPTY_ARRAY, FindInheritedValue::EmptyObjects::EMPTY_HASH

Constants included from Member::GraphQLTypeNames

Member::GraphQLTypeNames::Boolean, Member::GraphQLTypeNames::ID, Member::GraphQLTypeNames::Int

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Member::BaseDSLMethods

accessible?, default_graphql_name, description, graphql_name, introspection, introspection?, mutation, name, overridden_graphql_name, to_graphql, visible?

Methods included from Member::HasArguments

add_argument, all_argument_definitions, argument, argument_class, arguments_statically_coercible?, coerce_arguments, own_arguments, validate_directive_argument

Methods included from Member::HasValidators

validates, validators

Methods included from Member::HasPath

path

Constructor Details

#initialize(object:, context:, field:) ⇒ Resolver

Returns a new instance of Resolver.

Parameters:



34
35
36
37
38
39
40
41
42
43
44
# File 'lib/graphql/schema/resolver.rb', line 34

def initialize(object:, context:, field:)
  @object = object
  @context = context
  @field = field
  # Since this hash is constantly rebuilt, cache it for this call
  @arguments_by_keyword = {}
  self.class.arguments(context).each do |name, arg|
    @arguments_by_keyword[arg.keyword] = arg
  end
  @prepared_arguments = nil
end

Instance Attribute Details

#contextGraphQL::Query::Context (readonly)



50
51
52
# File 'lib/graphql/schema/resolver.rb', line 50

def context
  @context
end

#fieldGraphQL::Schema::Field (readonly)



58
59
60
# File 'lib/graphql/schema/resolver.rb', line 58

def field
  @field
end

#objectObject (readonly)

Returns The application object this field is being resolved on.

Returns:

  • (Object)

    The application object this field is being resolved on



47
48
49
# File 'lib/graphql/schema/resolver.rb', line 47

def object
  @object
end

Class Method Details

.argument(*args, **kwargs, &block) ⇒ Object

Add an argument to this field's signature, but also add some preparation hook methods which will be used for this argument

See Also:

  • for the signature


355
356
357
358
359
# File 'lib/graphql/schema/resolver.rb', line 355

def argument(*args, **kwargs, &block)
  # Use `from_resolver: true` to short-circuit the InputObject's own `loads:` implementation
  # so that we can support `#load_{x}` methods below.
  super(*args, from_resolver: true, **kwargs)
end

.broadcastable(new_broadcastable) ⇒ Object



273
274
275
# File 'lib/graphql/schema/resolver.rb', line 273

def broadcastable(new_broadcastable)
  @broadcastable = new_broadcastable
end

.broadcastable?Boolean?

Returns:



278
279
280
281
282
283
284
# File 'lib/graphql/schema/resolver.rb', line 278

def broadcastable?
  if defined?(@broadcastable)
    @broadcastable
  else
    (superclass.respond_to?(:broadcastable?) ? superclass.broadcastable? : nil)
  end
end

.complexity(new_complexity = nil) ⇒ Integer, Proc

Specifies the complexity of the field. Defaults to 1

Returns:

  • (Integer, Proc)


266
267
268
269
270
271
# File 'lib/graphql/schema/resolver.rb', line 266

def complexity(new_complexity = nil)
  if new_complexity
    @complexity = new_complexity
  end
  @complexity || (superclass.respond_to?(:complexity) ? superclass.complexity : 1)
end

.extension(extension, **options) ⇒ Object

Registers new extension

Parameters:

  • extension (Class)

    Extension class

  • options (Hash)

    Optional extension options



364
365
366
367
# File 'lib/graphql/schema/resolver.rb', line 364

def extension(extension, **options)
  @own_extensions ||= []
  @own_extensions << {extension => options}
end

.extensionsObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/graphql/schema/resolver.rb', line 370

def extensions
  own_exts = @own_extensions
  # Jump through some hoops to avoid creating arrays when we don't actually need them
  if superclass.respond_to?(:extensions)
    s_exts = superclass.extensions
    if own_exts
      if s_exts.any?
        own_exts + s_exts
      else
        own_exts
      end
    else
      s_exts
    end
  else
    own_exts || EMPTY_ARRAY
  end
end

.extras(new_extras = nil) ⇒ Object

Additional info injected into #resolve

See Also:

  • {GraphQL{GraphQL::Schema{GraphQL::Schema::Field{GraphQL::Schema::Field#extras}


218
219
220
221
222
223
224
# File 'lib/graphql/schema/resolver.rb', line 218

def extras(new_extras = nil)
  if new_extras
    @own_extras = new_extras
  end
  own_extras = @own_extras || []
  own_extras + (superclass.respond_to?(:extras) ? superclass.extras : [])
end

.field_optionsObject



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/graphql/schema/resolver.rb', line 307

def field_options

  all_args = {}
  all_argument_definitions.each do |arg|
    if (prev_entry = all_args[arg.graphql_name])
      if prev_entry.is_a?(Array)
        prev_entry << arg
      else
        all_args[arg.graphql_name] = [prev_entry, arg]
      end
    else
      all_args[arg.graphql_name] = arg
    end
  end

  field_opts = {
    type: type_expr,
    description: description,
    extras: extras,
    resolver_method: :resolve_with_support,
    resolver_class: self,
    arguments: all_args,
    null: null,
    complexity: complexity,
    broadcastable: broadcastable?,
  }

  # If there aren't any, then the returned array is `[].freeze`,
  # but passing that along breaks some user code.
  if (exts = extensions).any?
    field_opts[:extensions] = exts
  end

  if has_max_page_size?
    field_opts[:max_page_size] = max_page_size
  end

  field_opts
end

.has_max_page_size?Boolean

Returns true if this resolver or a superclass has an assigned max_page_size.

Returns:

  • (Boolean)

    true if this resolver or a superclass has an assigned max_page_size



303
304
305
# File 'lib/graphql/schema/resolver.rb', line 303

def has_max_page_size?
  defined?(@max_page_size) || (superclass.respond_to?(:has_max_page_size?) && superclass.has_max_page_size?)
end

.max_page_size(new_max_page_size = :not_given) ⇒ Integer?

Get or set the max_page_size: which will be configured for fields using this resolver (nil means "unlimited max page size".)

Parameters:

  • max_page_size (Integer, nil)

    Set a new value

Returns:

  • (Integer, nil)

    The max_page_size assigned to fields that use this resolver



290
291
292
293
294
295
296
297
298
299
300
# File 'lib/graphql/schema/resolver.rb', line 290

def max_page_size(new_max_page_size = :not_given)
  if new_max_page_size != :not_given
    @max_page_size = new_max_page_size
  elsif defined?(@max_page_size)
    @max_page_size
  elsif superclass.respond_to?(:max_page_size)
    superclass.max_page_size
  else
    nil
  end
end

.null(allow_null = nil) ⇒ Object

If true (default), then the return type for this resolver will be nullable. If false, then the return type is non-null.

Parameters:

  • allow_null (Boolean) (defaults to: nil)

    Whether or not the response can be null

See Also:

  • which sets the return type of this field and accepts a `null:` option


231
232
233
234
235
236
237
# File 'lib/graphql/schema/resolver.rb', line 231

def null(allow_null = nil)
  if !allow_null.nil?
    @null = allow_null
  end

  @null.nil? ? (superclass.respond_to?(:null) ? superclass.null : true) : @null
end

.resolve_method(new_method = nil) ⇒ Symbol

Default :resolve set below.

Returns:

  • (Symbol)

    The method to call on instances of this object to resolve the field



209
210
211
212
213
214
# File 'lib/graphql/schema/resolver.rb', line 209

def resolve_method(new_method = nil)
  if new_method
    @resolve_method = new_method
  end
  @resolve_method || (superclass.respond_to?(:resolve_method) ? superclass.resolve_method : :resolve)
end

.type(new_type = nil, null: nil) ⇒ Class

Call this method to get the return type of the field, or use it as a configuration method to assign a return type instead of generating one. TODO unify with #null

Parameters:

  • new_type (Class, Array<Class>, nil) (defaults to: nil)

    If a type definition class is provided, it will be used as the return type of the field

  • null (true, false) (defaults to: nil)

    Whether or not the field may return nil

Returns:

  • (Class)

    The type which this field returns.



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/graphql/schema/resolver.rb', line 246

def type(new_type = nil, null: nil)
  if new_type
    if null.nil?
      raise ArgumentError, "required argument `null:` is missing"
    end
    @type_expr = new_type
    @null = null
  else
    if @type_expr
      GraphQL::Schema::Member::BuildType.parse_type(@type_expr, null: @null)
    elsif superclass.respond_to?(:type)
      superclass.type
    else
      nil
    end
  end
end

.type_exprObject

A non-normalized type configuration, without null applied



348
349
350
# File 'lib/graphql/schema/resolver.rb', line 348

def type_expr
  @type_expr || (superclass.respond_to?(:type_expr) ? superclass.type_expr : nil)
end

Instance Method Details

#argumentsObject



60
61
62
# File 'lib/graphql/schema/resolver.rb', line 60

def arguments
  @prepared_arguments || raise("Arguments have not been prepared yet, still waiting for #load_arguments to resolve. (Call `.arguments` later in the code.)")
end

#authorized?(**inputs) ⇒ Boolean, early_return_data

Called after arguments are loaded, but before resolving.

Override it to check everything before calling the mutation.

Parameters:

  • inputs (Hash)

    The input arguments

Returns:

  • (Boolean, early_return_data)

    If false, execution will stop (and early_return_data will be returned instead, if present.)

Raises:



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/graphql/schema/resolver.rb', line 147

def authorized?(**inputs)
  self.class.arguments(context).each_value do |argument|
    arg_keyword = argument.keyword
    if inputs.key?(arg_keyword) && !(arg_value = inputs[arg_keyword]).nil? && (arg_value != argument.default_value)
      arg_auth, err = argument.authorized?(self, arg_value, context)
      if !arg_auth
        return arg_auth, err
      else
        true
      end
    else
      true
    end
  end
end

#dataloaderGraphQL::Dataloader

Returns:



53
54
55
# File 'lib/graphql/schema/resolver.rb', line 53

def dataloader
  context.dataloader
end

#ready?(**args) ⇒ Boolean, early_return_data

Called before arguments are prepared. Implement this hook to make checks before doing any work.

If it returns a lazy object (like a promise), it will be synced by GraphQL (but the resulting value won't be used).

Parameters:

  • args (Hash)

    The input arguments, if there are any

Returns:

  • (Boolean, early_return_data)

    If false, execution will stop (and early_return_data will be returned instead, if present.)

Raises:



136
137
138
# File 'lib/graphql/schema/resolver.rb', line 136

def ready?(**args)
  true
end

#resolve(**args) ⇒ Object

Do the work. Everything happens here.

Returns:

  • (Object)

    An object corresponding to the return type

Raises:



122
123
124
# File 'lib/graphql/schema/resolver.rb', line 122

def resolve(**args)
  raise GraphQL::RequiredImplementationMissingError, "#{self.class.name}#resolve should execute the field's logic"
end

#resolve_with_support(**args) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method is actually called by the runtime, it does some preparation and then eventually calls the user-defined #resolve method.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/graphql/schema/resolver.rb', line 68

def resolve_with_support(**args)
  # First call the ready? hook which may raise
  ready_val = if args.any?
    ready?(**args)
  else
    ready?
  end
  context.schema.after_lazy(ready_val) do |is_ready, ready_early_return|
    if ready_early_return
      if is_ready != false
        raise "Unexpected result from #ready? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{ready_early_return.inspect}]"
      else
        ready_early_return
      end
    elsif is_ready
      # Then call each prepare hook, which may return a different value
      # for that argument, or may return a lazy object
      load_arguments_val = load_arguments(args)
      context.schema.after_lazy(load_arguments_val) do |loaded_args|
        @prepared_arguments = loaded_args
        Schema::Validator.validate!(self.class.validators, object, context, loaded_args, as: @field)
        # Then call `authorized?`, which may raise or may return a lazy object
        authorized_val = if loaded_args.any?
          authorized?(**loaded_args)
        else
          authorized?
        end
        context.schema.after_lazy(authorized_val) do |(authorized_result, early_return)|
          # If the `authorized?` returned two values, `false, early_return`,
          # then use the early return value instead of continuing
          if early_return
            if authorized_result == false
              early_return
            else
              raise "Unexpected result from #authorized? (expected `true`, `false` or `[false, {...}]`): [#{authorized_result.inspect}, #{early_return.inspect}]"
            end
          elsif authorized_result
            # Finally, all the hooks have passed, so resolve it
            if loaded_args.any?
              public_send(self.class.resolve_method, **loaded_args)
            else
              public_send(self.class.resolve_method)
            end
          else
            raise GraphQL::UnauthorizedFieldError.new(context: context, object: object, type: field.owner, field: field)
          end
        end
      end
    end
  end
end

#unauthorized_object(err) ⇒ Object

Called when an object loaded by loads: fails the .authorized? check for its resolved GraphQL object type.

By default, the error is re-raised and passed along to GraphQL::Schema::Resolver.{Schema{Schema.unauthorized_object}.

Any value returned here will be used instead of of the loaded object.

Parameters:



169
170
171
# File 'lib/graphql/schema/resolver.rb', line 169

def unauthorized_object(err)
  raise err
end