class GraphQL::Schema::Resolver

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

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 {GraphQL::Schema::Mutation} for a concrete subclass of ‘Resolver`. @see {GraphQL::Function} `Resolver` is a replacement for `GraphQL::Function`

Attributes

context[R]

@return [GraphQL::Query::Context]

field[R]

@return [GraphQL::Schema::Field]

object[R]

@return [Object] The application object this field is being resolved on

Public Class Methods

new(object:, context:, field:) click to toggle source

@param object [Object] The application object that this field is being resolved on @param context [GraphQL::Query::Context] @param field [GraphQL::Schema::Field]

# File lib/graphql/schema/resolver.rb, line 32
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

Private Class Methods

all_field_argument_definitions() click to toggle source
# File lib/graphql/schema/resolver.rb, line 219
def all_field_argument_definitions
  all_argument_definitions
end
argument(*args, **kwargs, &block) click to toggle source

Add an argument to this field’s signature, but also add some preparation hook methods which will be used for this argument @see {GraphQL::Schema::Argument#initialize} for the signature

Calls superclass method
# File lib/graphql/schema/resolver.rb, line 360
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) click to toggle source
# File lib/graphql/schema/resolver.rb, line 297
def broadcastable(new_broadcastable)
  @broadcastable = new_broadcastable
end
broadcastable?() click to toggle source

@return [Boolean, nil]

# File lib/graphql/schema/resolver.rb, line 302
def broadcastable?
  if defined?(@broadcastable)
    @broadcastable
  else
    (superclass.respond_to?(:broadcastable?) ? superclass.broadcastable? : nil)
  end
end
complexity(new_complexity = nil) click to toggle source

Specifies the complexity of the field. Defaults to ‘1` @return [Integer, Proc]

# File lib/graphql/schema/resolver.rb, line 290
def complexity(new_complexity = nil)
  if new_complexity
    @complexity = new_complexity
  end
  @complexity || (superclass.respond_to?(:complexity) ? superclass.complexity : 1)
end
default_page_size(new_default_page_size = :not_given) click to toggle source

Get or set the ‘default_page_size:` which will be configured for fields using this resolver (`nil` means “unlimited default page size”.) @param default_page_size [Integer, nil] Set a new value @return [Integer, nil] The `default_page_size` assigned to fields that use this resolver

# File lib/graphql/schema/resolver.rb, line 335
def default_page_size(new_default_page_size = :not_given)
  if new_default_page_size != :not_given
    @default_page_size = new_default_page_size
  elsif defined?(@default_page_size)
    @default_page_size
  elsif superclass.respond_to?(:default_page_size)
    superclass.default_page_size
  else
    nil
  end
end
extension(extension, **options) click to toggle source

Registers new extension @param extension [Class] Extension class @param options [Hash] Optional extension options

# File lib/graphql/schema/resolver.rb, line 369
def extension(extension, **options)
  @own_extensions ||= []
  @own_extensions << {extension => options}
end
extensions() click to toggle source

@api private

# File lib/graphql/schema/resolver.rb, line 375
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) click to toggle source

Additional info injected into {#resolve} @see {GraphQL::Schema::Field#extras}

# File lib/graphql/schema/resolver.rb, line 234
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_arguments(context = GraphQL::Query::NullContext) click to toggle source
# File lib/graphql/schema/resolver.rb, line 211
def field_arguments(context = GraphQL::Query::NullContext)
  arguments(context)
end
get_field_argument(name, context = GraphQL::Query::NullContext) click to toggle source
# File lib/graphql/schema/resolver.rb, line 215
def get_field_argument(name, context = GraphQL::Query::NullContext)
  get_argument(name, context)
end
has_default_page_size?() click to toggle source

@return [Boolean] ‘true` if this resolver or a superclass has an assigned `default_page_size`

# File lib/graphql/schema/resolver.rb, line 348
def has_default_page_size?
  (!!defined?(@default_page_size)) || (superclass.respond_to?(:has_default_page_size?) && superclass.has_default_page_size?)
end
has_max_page_size?() click to toggle source

@return [Boolean] ‘true` if this resolver or a superclass has an assigned `max_page_size`

# File lib/graphql/schema/resolver.rb, line 327
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) click to toggle source

Get or set the ‘max_page_size:` which will be configured for fields using this resolver (`nil` means “unlimited max page size”.) @param max_page_size [Integer, nil] Set a new value @return [Integer, nil] The `max_page_size` assigned to fields that use this resolver

# File lib/graphql/schema/resolver.rb, line 314
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) click to toggle source

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

@see type which sets the return type of this field and accepts a ‘null:` option @param allow_null [Boolean] Whether or not the response can be null

# File lib/graphql/schema/resolver.rb, line 247
def null(allow_null = nil)
  if !allow_null.nil?
    @null = allow_null
  end

  @null.nil? ? (superclass.respond_to?(:null) ? superclass.null : true) : @null
end
own_extensions() click to toggle source
# File lib/graphql/schema/resolver.rb, line 396
def own_extensions
  @own_extensions
end
resolve_method(new_method = nil) click to toggle source

Default ‘:resolve` set below. @return [Symbol] The method to call on instances of this object to resolve the field

# File lib/graphql/schema/resolver.rb, line 225
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
resolver_method(new_method_name = nil) click to toggle source
# File lib/graphql/schema/resolver.rb, line 255
def resolver_method(new_method_name = nil)
  if new_method_name
    @resolver_method = new_method_name
  else
    @resolver_method || :resolve_with_support
  end
end
type(new_type = nil, null: nil) click to toggle source

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} @param new_type [Class, Array<Class>, nil] If a type definition class is provided, it will be used as the return type of the field @param null [true, false] Whether or not the field may return ‘nil` @return [Class] The type which this field returns.

# File lib/graphql/schema/resolver.rb, line 270
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: self.null)
    elsif superclass.respond_to?(:type)
      superclass.type
    else
      nil
    end
  end
end
type_expr() click to toggle source

A non-normalized type configuration, without ‘null` applied

# File lib/graphql/schema/resolver.rb, line 353
def type_expr
  @type_expr || (superclass.respond_to?(:type_expr) ? superclass.type_expr : nil)
end

Public Instance Methods

arguments() click to toggle source
# File lib/graphql/schema/resolver.rb, line 58
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) click to toggle source

Called after arguments are loaded, but before resolving.

Override it to check everything before calling the mutation. @param inputs [Hash] The input arguments @raise [GraphQL::ExecutionError] To add an error to the response @raise [GraphQL::UnauthorizedError] To signal an authorization failure @return [Boolean, early_return_data] If ‘false`, execution will stop (and `early_return_data` will be returned instead, if present.)

# File lib/graphql/schema/resolver.rb, line 145
def authorized?(**inputs)
  arg_owner = @field # || self.class
  args = arg_owner.arguments(context)
  authorize_arguments(args, inputs)
end
dataloader() click to toggle source

@return [GraphQL::Dataloader]

# File lib/graphql/schema/resolver.rb, line 51
def dataloader
  context.dataloader
end
ready?(**args) click to toggle source

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).

@param args [Hash] The input arguments, if there are any @raise [GraphQL::ExecutionError] To add an error to the response @raise [GraphQL::UnauthorizedError] To signal an authorization failure @return [Boolean, early_return_data] If ‘false`, execution will stop (and `early_return_data` will be returned instead, if present.)

# File lib/graphql/schema/resolver.rb, line 134
def ready?(**args)
  true
end
resolve(**args) click to toggle source

Do the work. Everything happens here. @return [Object] An object corresponding to the return type

# File lib/graphql/schema/resolver.rb, line 120
def resolve(**args)
  raise GraphQL::RequiredImplementationMissingError, "#{self.class.name}#resolve should execute the field's logic"
end
resolve_with_support(**args) click to toggle source

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

# File lib/graphql/schema/resolver.rb, line 66
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, {...}]`): [#{is_ready.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) click to toggle source

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 {{Schema.unauthorized_object}}.

Any value returned here will be used _instead of_ of the loaded object. @param err [GraphQL::UnauthorizedError]

# File lib/graphql/schema/resolver.rb, line 157
def unauthorized_object(err)
  raise err
end

Private Instance Methods

authorize_arguments(args, inputs) click to toggle source
# File lib/graphql/schema/resolver.rb, line 163
def authorize_arguments(args, inputs)
  args.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
get_argument(name, context = GraphQL::Query::NullContext) click to toggle source
# File lib/graphql/schema/resolver.rb, line 206
def get_argument(name, context = GraphQL::Query::NullContext)
  self.class.get_argument(name, context)
end
load_arguments(args) click to toggle source
# File lib/graphql/schema/resolver.rb, line 179
def load_arguments(args)
  prepared_args = {}
  prepare_lazies = []

  args.each do |key, value|
    arg_defn = @arguments_by_keyword[key]
    if arg_defn
      prepped_value = prepared_args[key] = arg_defn.load_and_authorize_value(self, value, context)
      if context.schema.lazy?(prepped_value)
        prepare_lazies << context.schema.after_lazy(prepped_value) do |finished_prepped_value|
          prepared_args[key] = finished_prepped_value
        end
      end
    else
      # these are `extras:`
      prepared_args[key] = value
    end
  end

  # Avoid returning a lazy if none are needed
  if prepare_lazies.any?
    GraphQL::Execution::Lazy.all(prepare_lazies).then { prepared_args }
  else
    prepared_args
  end
end