▄██████▄ ▄████████ ▄████████ ▄███████▄ ▄█ █▄ ████████▄ ▄█ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▀ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ▄███ ▄███▄▄▄▄██▀ ███ ███ ███ ███ ▄███▄▄▄▄███▄▄ ███ ███ ███ ▀▀███ ████▄ ▀▀███▀▀▀▀▀ ▀███████████ ▀█████████▀ ▀▀███▀▀▀▀███▀ ███ ███ ███ ███ ███ ▀███████████ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ▀ ███ ███▌ ▄ ████████▀ ███ ███ ███ █▀ ▄████▀ ███ █▀ ▀██████▀▄█ █████▄▄██ ███ ███ ▀

A GraphQL server implementation for Ruby

Schema — Types and Fields

Types, fields and arguments make up a schema’s type system. These objects are also open to extension via metadata and accepts_definitions.

Types

Types describe objects and values in a system. The API documentation for each type contains a detailed description with examples.

Objects are described with GraphQL::ObjectTypes.

Scalar values are described with built-in scalars (string, int, float, boolean, ID) or custom GraphQL::EnumTypes. You can define custom GraphQL::ScalarTypes, too.

Scalars and enums can be sent to GraphQL as inputs. For complex inputs (key-value pairs), use GraphQL::InputObjectType.

There are two abstract types, too:

  • GraphQL::InterfaceType describes a collection of object types which implement some of the same fields.
  • GraphQL::UnionType describes a collection of object types which may appear in the same place in the schema (ie, may be returned by the same field.)

GraphQL::ListType and GraphQL::NonNullType modify other types, describing them as “list of T” or “required T”.

Referencing Types

Some parts of schema definition take types as an input. There are two good ways to provide types:

  1. By value. Pass a variable which holds the type.

    # constant
    field :team, TeamType
    # local variable
    field :stadium, stadium_type
    
  2. By proc, which will be lazy-evaluated to look up a type.

    field :team, -> { TeamType }
    field :stadium, -> { LookupTypeForModel.lookup(Stadium) }
    

Fields

GraphQL::ObjectTypes and GraphQL::InterfaceTypes may expose their values with fields. A field definition looks like this:

PostType = GraphQL::ObjectType.define do
  # ...
  #     name  , type        , description (optional)
  field :title, types.String, "The title of the Post"
end

By default, fields are resolved by sending the name to the underlying object (eg post.title in the example above).

You can define a different resolution by providing a resolve function:

PostType = GraphQL::ObjectType.define do
  # ...
  #     name   , type        , description (optional)
  field :teaser, types.String, "The teaser of the Post" do
    # how to get the value?
    resolve ->(obj, args, ctx) {
      # first 40 chars of the body
      obj.body[0, 40]
    }
  end
end

The resolve function receives inputs:

In fact, the field do ... end block is passed to GraphQL::Field’s .define method, so you can define many things there:

field do
  name "teaser"
  type types.String
  description "..."
  resolve ->(obj, args, ctx) { ... }
  deprecation_reason "Too long, use .title instead"
  complexity 2
end

Arguments

Fields can take arguments as input. These can be used to determine the return value (eg, filtering search results) or to modify the application state (eg, updating the database in MutationType).

Arguments are defined with the argument helper:

field :search_posts, types[PostType] do
  argument :category, types.String
  resolve ->(obj, args, ctx) {
    args[:category]
    # => maybe a string, eg "Programming"
    if args[:category]
      Post.where(category: category).limit(10)
    else
      Post.all.limit(10)
    end
  }
end

Use ! to mark an argument as required:

# This argument is a required string:
argument :category, !types.String

Use as: :alternateName to use a different key from within your resolvers while exposing another key to clients.

field :post, PostType do
  argument :postId, types.Id, as: :id
  resolve ->(obj, args, ctx) {
    Post.find(args['id'])
  }
end

Provide a prepare function to modify or validate the value of an argument before the field’s resolve function is executed:

field :posts, types[PostType] do
  argument :startDate, types.String, prepare: ->(startDate) {
    # return the prepared argument or GraphQL::ExecutionError.new("msg")
    # to halt the execution of the field and add "msg" to the `errors` key.
  }
  resolve ->(obj, args, ctx) {
    # use prepared args['startDate']
  }
end

Only certain types are valid for arguments:

The args parameter of a resolve function will always be a GraphQL::Query::Arguments. You can access specific arguments with ["arg_name"] or [:arg_name]. You recursively turn it into a Ruby Hash with to_h. Inside args, scalars will be parsed into Ruby values and enums will be converted to their value: (if one was provided).

resolve ->(obj, args, ctx) {
  args["category"] == args[:category]
  # => true
  args.to_h
  # => { "category" => "Programming" }
  # ...
}

Extending type and field definitions

Types, fields, and arguments have a metadata hash which accepts values during definition.

First, make a custom definition:

GraphQL::ObjectType.accepts_definitions resolves_to_class_names: GraphQL::Define.assign_metadata_key(:resolves_to_class_names)
# or:
# GraphQL::Field.accepts_definitions(...)
# GraphQL::Argument.accepts_definitions(...)

MySchema = GraphQL::Schema.define do
  # ...
end

Then, use the custom definition:

Post = GraphQL::ObjectType.define do
  # ...
  resolves_to_class_names ["Post", "StaffUpdate"]
end

Access type.metadata later:

MySchema = GraphQL::Schema.define do
  # ...
  # Use the type's declared `resolves_to_class_names`
  # to figure out if `obj` is a member of that type
  resolve_type ->(obj, ctx) {
    class_name = obj.class.name
    MySchema.types.values.find { |type| type.metadata[:resolves_to_class_names].include?(class_name) }
  }
end

This behavior is provided by GraphQL::Define::InstanceDefinable.