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:
GraphQL::Schema::Mutation
, a bare-bones base classGraphQL::Schema::RelayClassicMutation
, a base class with a set of nice conventions that also supports the Relay Classic mutation specification.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
.
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.)
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
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).