Mutation is a special type of query used to change data in the database like Creating, Editing or Deleting Records from a table or Store. These are the equivalent to the POST, PUT, PATCH and DELETE in HTTP/REST speak. Defining mutations is very similar to defining queries. The only difference is how you implement the logic inside the mutation. In mutation, we can control and specify the output data that API need to return after mutation procedure.
In this article, I am Adding a mutation query to add comments to an article which we discussed in previous example.
To add mutations to your GraphQL schema, first we need to define a mutation type in mutations folder
Like QueryType, MutationType is a root of the schema. Members of MutationType are mutation fields. For GraphQL in general, mutation fields are identical to query fields except that they have side-effects (which mutate application state, eg, update the database).
Since we created new folder for mutations, we have to tell Rails to autoload paths. Put below code in application.rb to autoload it.
Now we need to define specific mutation query. Following are the process to define a mutation - give operation a name - declare its inputs - declare its outputs - declare the mutation procedure in resolve block. resolve should return a hash with a key for each of the return_fields
In out example, we need to define CommentMutations in mutations folder.
Here input_field specify the input params we can pass in the query. In return_field, we can specify the fields returning after the update. Inside resolve block, we define the business logic. and resolve should return a hash with a key for each of the return_fields.
After defining this, we need to add the mutation’s derived field to the mutation type.
app/graphql/mutations/mutation_type.rb
12345
MutationType=GraphQL::ObjectType.definedoname"Mutation"# Add the mutation's derived field to the mutation typefield:addComment,field:CommentMutations::Create.fieldend
{"data":{"addComment":{"article":{"id":1,"comments":[{"comment":"Good article","user":{"name":"Shaiju E"}},{"comment":"Keep going","user":{"name":"Shaiju E"}},{"comment":"Another Comment","user":{"name":"David"}},{"comment":"New Comment","user":{"name":"Shaiju E"}},{"comment":"Another Comment from User 2","user":{"name":"David"}},{"comment":"Another Comment from User 1","user":{"name":"Shaiju E"}},{"comment":"TEST","user":{"name":"Shaiju E"}},{"comment":"New comment","user":{"name":"Shaiju E"}}]}}}}
We can call the same query by passing inputs using variables
$comments: AddCommentInput! will configure the variable $comments to take values from query variables section. input: $comments will pass $comments as input to mutation query.
Lets write another example for updation mutation. If we want to update a comment, we need to write UpdateComment mutation in comment_mutations.rb
# encoding: utf-8moduleCommentMutationsCreate=GraphQL::Relay::Mutation.definedoname"AddComment"# Define input parametersinput_field:articleId,!types.IDinput_field:userId,!types.IDinput_field:comment,!types.String# Define return parametersreturn_field:article,ArticleTypereturn_field:errors,types.Stringresolve->(object,inputs,ctx){article=Article.find_by_id(inputs[:articleId])return{errors:'Article not found'}ifarticle.nil?comments=article.commentsnew_comment=comments.build(user_id:inputs[:userId],comment:inputs[:comment])ifnew_comment.save{article:article}else{errors:new_comment.errors.to_a}end}endUpdate=GraphQL::Relay::Mutation.definedoname"UpdateComment"# Define input parametersinput_field:id,!types.IDinput_field:comment,types.IDinput_field:userId,types.IDinput_field:articleId,types.ID# Define return parametersreturn_field:comment,CommentTypereturn_field:errors,types.Stringresolve->(object,inputs,ctx){comment=Comment.find_by_id(inputs[:id])return{errors:'Comment not found'}ifcomment.nil?valid_inputs=ActiveSupport::HashWithIndifferentAccess.new(inputs.instance_variable_get(:@original_values).select{|k,_|comment.respond_to?"#{k}=".underscore}).except(:id)ifcomment.update_attributes(valid_inputs){comment:comment}else{errors:comment.errors.to_a}end}endend
Main defference here is, we need to create valid_inputs. This will allow us mass assignment with update attributes with valied fields which we passed.
After defining this, we need to add the mutation’s derived field to the mutation type.
app/graphql/mutations/mutation_type.rb
123456
MutationType=GraphQL::ObjectType.definedoname"Mutation"# Add the mutation's derived field to the mutation typefield:addComment,field:CommentMutations::Create.fieldfield:updateComment,field:CommentMutations::Update.fieldend
Mutation for delete a comment and return post and deleted comment ID
app/graphql/mutations/comment_mutations.rb
123456789101112131415161718192021222324252627
# encoding: utf-8moduleCommentMutationsDestroy=GraphQL::Relay::Mutation.definedoname'DestroyComment'description'Delete a comment and return post and deleted comment ID'# Define input parametersinput_field:id,!types.ID# Define return parametersreturn_field:deletedId,!types.IDreturn_field:article,ArticleTypereturn_field:errors,types.Stringresolve->(_obj,inputs,ctx){comment=Comment.find_by_id(inputs[:id])return{errors:'Comment not found'}ifcomment.nil?article=comment.articlecomment.destroy{article:article.reload,deletedId:inputs[:id]}}end# Other mutations defined here....end
app/graphql/mutations/mutation_type.rb
1234567
MutationType=GraphQL::ObjectType.definedoname"Mutation"# Add the mutation's derived field to the mutation typefield:addComment,field:CommentMutations::Create.fieldfield:updateComment,field:CommentMutations::Update.fieldfield:destroyComment,field:CommentMutations::Destroy.fieldend