class GraphQL::Schema::Field
def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, **kwargs, &block)
- See: {.initialize} - for other options
Returns:
-
(GraphQL::Schema:Field)
- an instance of `self
Parameters:
-
mutation
(Class
) -- A {GraphQL::Schema::Mutation} class to use for field configuration -
resolver
(Class
) -- A {GraphQL::Schema::Resolver} class to use for field configuration
def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, **kwargs, &block) if (parent_config = resolver || mutation) # Get the parent config, merge in local overrides kwargs = parent_config.field_options.merge(kwargs) # Add a reference to that parent class kwargs[:resolver_class] = parent_config end if name kwargs[:name] = name end if !type.nil? if type.is_a?(GraphQL::Field) raise ArgumentError, "A GraphQL::Field was passed as the second argument, use the `field:` keyword for this instead." end if desc if kwargs[:description] raise ArgumentError, "Provide description as a positional argument or `description:` keyword, but not both (#{desc.inspect}, #{kwargs[:description].inspect})" end kwargs[:description] = desc kwargs[:type] = type elsif (kwargs[:field] || kwargs[:function] || resolver || mutation) && type.is_a?(String) # The return type should be copied from `field` or `function`, and the second positional argument is the description kwargs[:description] = type else kwargs[:type] = type end end new(**kwargs, &block) end
def accessible?(context)
def accessible?(context) if @resolver_class @resolver_class.accessible?(context) else true end end
def apply_scope(value, ctx)
def apply_scope(value, ctx) if scoped? ctx.schema.after_lazy(value) do |inner_value| @type.unwrap.scope_items(inner_value, ctx) end else value end end
def authorized?(object, context)
def authorized?(object, context) if @resolver_class @resolver_class.authorized?(object, context) else true end end
def complexity(new_complexity)
def complexity(new_complexity) case new_complexity when Proc if new_complexity.parameters.size != 3 fail( "A complexity proc should always accept 3 parameters: ctx, args, child_complexity. "\ "E.g.: complexity ->(ctx, args, child_complexity) { child_complexity * args[:limit] }" ) else @complexity = new_complexity end when Numeric @complexity = new_complexity else raise("Invalid complexity: #{new_complexity.inspect} on #{@name}") end end
def connection?
-
(Boolean)
- if true, this field will be wrapped with Relay connection behavior
def connection? if @connection.nil? # Provide default based on type name return_type_name = if (contains_type = @field || @function) Member::BuildType.to_type_name(contains_type.type) elsif @return_type_expr Member::BuildType.to_type_name(@return_type_expr) else raise "No connection info possible" end @connection = return_type_name.end_with?("Connection") else @connection end end
def description(text = nil)
-
(String)
-
Parameters:
-
text
(String
) --
def description(text = nil) if text @description = text else @description end end
def fetch_extra(extra_name, ctx)
-
ctx
(GraphQL::Query::Context::FieldResolutionContext
) --
def fetch_extra(extra_name, ctx) if !CONTEXT_EXTRAS.include?(extra_name) && respond_to?(extra_name) self.public_send(extra_name) elsif ctx.respond_to?(extra_name) ctx.public_send(extra_name) else raise NotImplementedError, "Unknown field extra for #{self.path}: #{extra_name.inspect}" end end
def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, scope: nil, resolve: nil, introspection: false, hash_key: nil, camelize: true, trace: nil, complexity: 1, extras: [], resolver_class: nil, subscription_scope: nil, arguments: {}, &definition_block)
-
subscription_scope
(Symbol, String
) -- A key in `context` which will be used to scope subscription payloads -
scope
(Boolean
) -- If true, the return type's `.scope_items` method will be called on the return value -
complexity
(Numeric
) -- When provided, set the complexity for this field -
camelize
(Boolean
) -- If true, the field name will be camelized when building the schema -
arguments
({String=>GraphQL::Schema::Argument, Hash}
) -- Arguments for this field (may be added in the block, also) -
resolver_class
(Class
) -- (Private) A {Schema::Resolver} which this field was derived from. Use `resolver:` to create a field with a resolver. -
function
(GraphQL::Function
) -- **deprecated** for compatibility with <1.8.0 -
field
(GraphQL::Field, GraphQL::Schema::Field
) -- **deprecated** for compatibility with <1.8.0 -
resolve
(<#call(obj, args, ctx)>
) -- **deprecated** for compatibility with <1.8.0 -
introspection
(Boolean
) -- If true, this field will be marked as `#introspection?` and the name may begin with `__` -
max_page_size
(Integer
) -- For connections, the maximum number of items to return from this field -
connection
(Boolean
) -- `true` if this field should get automagic connection behavior; default is to infer by `*Connection` in the return type name -
hash_key
(Object
) -- The hash key to lookup to resolve this field (defaults to `name` or `name.to_s`) -
method
(Symbol
) -- The method to call to resolve this field (defaults to `name`) -
deprecation_reason
(String
) -- If present, the field is marked "deprecated" with this message -
description
(String
) -- Field description -
null
(Boolean
) -- `true` if this field may return `null`, `false` if it is never `null` -
owner
(Class
) -- The type that this field belongs to -
desc
(String
) -- Field description -
return_type_expr
(Class, GraphQL::BaseType, Array
) -- The return type of this field -
name
(Symbol
) -- The underscore-cased version of this field name (will be camelized for the GraphQL API)
def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, connection: nil, max_page_size: nil, scope: nil, resolve: nil, introspection: false, hash_key: nil, camelize: true, trace: nil, complexity: 1, extras: [], resolver_class: nil, subscription_scope: nil, arguments: {}, &definition_block) if name.nil? raise ArgumentError, "missing first `name` argument or keyword `name:`" end if !(field || function || resolver_class) if type.nil? raise ArgumentError, "missing second `type` argument or keyword `type:`" end if null.nil? raise ArgumentError, "missing keyword argument null:" end end if (field || function || resolve) && extras.any? raise ArgumentError, "keyword `extras:` may only be used with method-based resolve and class-based field such as mutation class, please remove `field:`, `function:` or `resolve:`" end @original_name = name @name = camelize ? Member::BuildType.camelize(name.to_s) : name.to_s @description = description if field.is_a?(GraphQL::Schema::Field) @field_instance = field else @field = field end @function = function @resolve = resolve @deprecation_reason = deprecation_reason if method && hash_key raise ArgumentError, "Provide `method:` _or_ `hash_key:`, not both. (called with: `method: #{method.inspect}, hash_key: #{hash_key.inspect}`)" end # TODO: I think non-string/symbol hash keys are wrongly normalized (eg `1` will not work) method_name = method || hash_key || Member::BuildType.underscore(name.to_s) @method_str = method_name.to_s @method_sym = method_name.to_sym @complexity = complexity @return_type_expr = type @return_type_null = null @connection = connection @max_page_size = max_page_size @introspection = introspection @extras = extras @resolver_class = resolver_class @scope = scope @trace = trace # Override the default from HasArguments @own_arguments = {} arguments.each do |name, arg| if arg.is_a?(Hash) argument(name: name, **arg) else @own_arguments[name] = arg end end @owner = owner @subscription_scope = subscription_scope if definition_block if definition_block.arity == 1 yield self else instance_eval(&definition_block) end end end
def public_send_field(obj, graphql_args, field_ctx)
def public_send_field(obj, graphql_args, field_ctx) if graphql_args.any? || @extras.any? # Splat the GraphQL::Arguments to Ruby keyword arguments ruby_kwargs = graphql_args.to_kwargs # Apply any `prepare` methods. Not great code organization, can this go somewhere better? arguments.each do |name, arg_defn| ruby_kwargs_key = arg_defn.keyword if ruby_kwargs.key?(ruby_kwargs_key) && arg_defn.prepare ruby_kwargs[ruby_kwargs_key] = arg_defn.prepare_value(obj, ruby_kwargs[ruby_kwargs_key]) end end if connection? # Remove pagination args before passing it to a user method ruby_kwargs.delete(:first) ruby_kwargs.delete(:last) ruby_kwargs.delete(:before) ruby_kwargs.delete(:after) end @extras.each do |extra_arg| ruby_kwargs[extra_arg] = fetch_extra(extra_arg, field_ctx) end else ruby_kwargs = NO_ARGS end if ruby_kwargs.any? obj.public_send(@method_sym, **ruby_kwargs) else obj.public_send(@method_sym) end end
def resolve_field(obj, args, ctx)
Implement {GraphQL::Field}'s resolve API.
def resolve_field(obj, args, ctx) ctx.schema.after_lazy(obj) do |after_obj| # First, apply auth ... query_ctx = ctx.query.context # Some legacy fields can have `nil` here, not exactly sure why. # @see https://github.com/rmosolgo/graphql-ruby/issues/1990 before removing inner_obj = after_obj && after_obj.object if authorized?(inner_obj, query_ctx) && arguments.each_value.all? { |a| a.authorized?(inner_obj, query_ctx) } # Then if it passed, resolve the field v = if @resolve_proc # Might be nil, still want to call the func in that case @resolve_proc.call(inner_obj, args, ctx) elsif @resolver_class singleton_inst = @resolver_class.new(object: inner_obj, context: query_ctx) public_send_field(singleton_inst, args, ctx) else public_send_field(after_obj, args, ctx) end apply_scope(v, ctx) else nil end end end
def resolve_field_method(obj, ruby_kwargs, ctx)
-
ctx
(GraphQL::Query::Context
) -- -
ruby_kwargs
(Hash
) --Object> -
obj
(GraphQL::Schema::Object
) --
def resolve_field_method(obj, ruby_kwargs, ctx) if obj.object.is_a?(Hash) inner_object = obj.object if inner_object.key?(@method_sym) inner_object[@method_sym] else inner_object[@method_str] end elsif obj.object.respond_to?(@method_sym) if ruby_kwargs.any? obj.object.public_send(@method_sym, **ruby_kwargs) else obj.object.public_send(@method_sym) end else raise <<-ERR Failed to implement #{@owner.graphql_name}.#{@name}, tried: - `#{obj.class}##{@method_sym}`, which did not exist - `#{obj.object.class}##{@method_sym}`, which did not exist - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash To implement this field, define one of the methods above (and check for typos) ERR end end
def resolver
-
(Class, nil)
- The {Schema::Resolver} this field was derived from, if there is one
def resolver @resolver_class end
def scoped?
-
(Boolean)
- if true, the return type's `.scope_items` method will be applied to this field's return value
def scoped? if !@scope.nil? # The default was overridden @scope else @return_type_expr && type.unwrap.respond_to?(:scope_items) && (connection? || type.list?) end end
def to_graphql
-
(GraphQL::Field)
-
def to_graphql # this field was previously defined and passed here, so delegate to it if @field_instance return @field_instance.to_graphql end field_defn = if @field @field.dup elsif @function GraphQL::Function.build_field(@function) else GraphQL::Field.new end field_defn.name = @name if @return_type_expr field_defn.type = -> { type } end if @description field_defn.description = @description end if @deprecation_reason field_defn.deprecation_reason = @deprecation_reason end if @resolver_class if @resolver_class < GraphQL::Schema::Mutation field_defn.mutation = @resolver_class end field_defn.metadata[:resolver] = @resolver_class end if !@trace.nil? field_defn.trace = @trace end field_defn.resolve = self.method(:resolve_field) field_defn.connection = connection? field_defn.connection_max_page_size = @max_page_size field_defn.introspection = @introspection field_defn.complexity = @complexity field_defn.subscription_scope = @subscription_scope # apply this first, so it can be overriden below if connection? # TODO: this could be a bit weird, because these fields won't be present # after initialization, only in the `to_graphql` response. # This calculation _could_ be moved up if need be. argument :after, "String", "Returns the elements in the list that come after the specified cursor.", required: false argument :before, "String", "Returns the elements in the list that come before the specified cursor.", required: false argument :first, "Int", "Returns the first _n_ elements from the list.", required: false argument :last, "Int", "Returns the last _n_ elements from the list.", required: false end arguments.each do |name, defn| arg_graphql = defn.to_graphql field_defn.arguments[arg_graphql.name] = arg_graphql end # Support a passed-in proc, one way or another @resolve_proc = if @resolve @resolve elsif @function @function elsif @field @field.resolve_proc end # Ok, `self` isn't a class, but this is for consistency with the classes field_defn.metadata[:type_class] = self field_defn end
def type
def type @type ||= Member::BuildType.parse_type(@return_type_expr, null: @return_type_null) rescue raise ArgumentError, "Failed to build return type for #{@owner.graphql_name}.#{name} from #{@return_type_expr.inspect}: #{$!.message}", $!.backtrace end
def visible?(context)
def visible?(context) if @resolver_class @resolver_class.visible?(context) else true end end