Mutation Classes

GraphQL mutations are special fields: instead of reading data or performing calculations, they may modify the application state. For example, mutation fields may:

These actions are called side effects.

Like all GraphQL fields, mutation fields:

GraphQL-Ruby includes two classes to help you write mutations:

Besides those, you can also use the plain field API to write mutation fields.

An additional null helper method is provided on classes inheriting from GraphQL::Schema::Mutation to allow setting the nullability of the mutation. This is not required and defaults to true.

Example mutation class

If you used the install generator, a base mutation class will already have been generated for you. If that’s not the case, you should add a base class to your application, for example:

class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation
  # Add your custom classes if you have them:
  # This is used for generating payload types
  object_class Types::BaseObject
  # This is used for return fields on the mutation's payload
  field_class Types::BaseField
  # This is used for generating the `input: { ... }` object type
  input_object_class Types::BaseInputObject
end

Then extend it for your mutations:

class Mutations::CreateComment < Mutations::BaseMutation
  null true

  argument :body, String, required: true
  argument :post_id, ID, required: true

  field :comment, Types::Comment, null: true
  field :errors, [String], null: false

  def resolve(body:, post_id:)
    post = Post.find(post_id)
    comment = post.comments.build(body: body, author: context[:current_user])
    if comment.save
      # Successful creation, return the created object with no errors
      {
        comment: comment,
        errors: [],
      }
    else
      # Failed save, return the errors to the client
      {
        comment: nil,
        errors: comment.errors.full_messages
      }
    end
  end
end

The #resolve method should return a hash whose symbols match the field names.

(See Mutation Errors%20for%20more%20information%20about%20returning%20errors.)

Hooking up mutations

Mutations must be attached to the mutation root using the mutation: keyword, for example:

class Types::Mutation < Types::BaseObject
  field :create_comment, mutation: Mutations::CreateComment
end

Auto-loading arguments

In most cases, a GraphQL mutation will act against a given global relay ID. Loading objects from these global relay IDs can require a lot of boilerplate code in the mutation’s resolver.

An alternative approach is to use the loads: argument when defining the argument:

class Mutations::AddStar < Mutations::BaseMutation
  argument :post_id, ID, required: true, loads: Types::Post

  field :post, Types::Post, null: true

  def resolve(post:)
    post.star

    {
      post: post,
    }
  end
end

By specifying that the post_id argument loads a Types::Post object type, a Post object will be loaded via Schema#object_from_id with the provided post_id.

All arguments that end in _id and use the loads: method will have their _id suffix removed. For example, the mutation resolver above receives a post argument which contains the loaded object, instead of a post_id argument.

The loads: option also works with list of IDs, for example:

class Mutations::AddStars < Mutations::BaseMutation
  argument :post_ids, [ID], required: true, loads: Types::Post

  field :posts, [Types::Post], null: true

  def resolve(posts:)
    posts.map(&:star)

    {
      posts: posts,
    }
  end
end

All arguments that end in _ids and use the loads: method will have their _ids suffix removed and an s appended to their name. For example, the mutation resolver above receives a posts argument which contains all the loaded objects, instead of a post_ids argument.

In some cases, you may want to control the resulting argument name. This can be done using the as: argument, for example:

class Mutations::AddStar < Mutations::BaseMutation
  argument :post_id, ID, required: true, loads: Types::Post, as: :something

  field :post, Types::Post, null: true

  def resolve(something:)
    something.star

    {
      post: something
    }
  end
end

In the above examples, loads: is provided a concrete type, but it also supports abstract types (i.e. interfaces and unions).