Previous: Chapter 4 - The Individual
📝 As an app user, I want to create a new user, so that that new engineers are available to host All Hands.
So far we’re only fetching data through graphql queries, but this user story will require a mutation. Mutations are used to create, update, and destroy records and in this chapter we’ll create our first mutation class.
Check out the available generators by using the --help flag.
Terminal
bin/rails g --help
Use the graphql:mutation_create generator.
Terminal
bin/rails g graphql:mutation_create User
You’ll see that the generator modified app/graphql/types/mutation_type.rb and created app/graphql/mutations/user_create.rb. Looking at our new mutation class, we can see that most of the work happens in the resolve method. If the new user fails to save, we raise a GraphQL::ExecutionError error. If the new user saves just fine, we return a response with the user field set. The mutation is nearly complete and we got it for free, but let’s add our own flavor to it. We can make error handling more generic, like we did in Chapter 4, and replace the existing lines with a single create! call. We also see a new input type, Types::UserInputType, that we’ll need to generate.
Clean up Types::MutationType a little.
app/graphql/types/mutation_type.rb
module Types
class MutationType < Types::BaseObject
field :user_create, mutation: Mutations::UserCreate
end
end
Make some changes to Mutations::UserCreate as well.
app/graphql/mutations/user_create.rb
module Mutations
class UserCreate < BaseMutation
description "Creates a new user"
field :user, Types::UserType, null: false
argument :user_input, Types::UserInputType, required: true
def resolve(user_input:)
user = User.create!(**user_input)
{ user: user }
end
end
end
Add some global error handling.
app/graphql/all_hands_schema.rb (lines omitted)
class AllHandsSchema < GraphQL::Schema
# ...
rescue_from(ActiveRecord::RecordInvalid) do |err, obj, args, ctx, field|
raise GraphQL::ExecutionError.new(
"#{err.record.class.name} invalid",
extensions: err.record.errors.to_hash,
)
end
# ...
end
Generate the input type.
Terminal
bin/rails g graphql:input User
We can see that our new class picked up all User attributes, but we only intend to accept name and email. We also intend to require name and email.
Make those changes.
app/graphql/types/user_input_type.rb
module Types
class UserInputType < Types::BaseInputObject
argument :name, String, required: true
argument :email, String, required: true
end
end
Generate a test for the mutation.
Terminal
bin/rails g integration_test types/mutation_type/user_create
Fill in the generated test.
test/integration/types/mutation_type/user_create_test.rb
require "test_helper"
class Types::MutationType::UserCreateTest < ActionDispatch::IntegrationTest
setup do
@query = <<~GRAPHQL
mutation UserCreate($name: String!, $email: String!) {
userCreate(input: {
userInput: {
name: $name
email: $email
}
}) {
user {
id
name
email
createdAt
updatedAt
}
}
}
GRAPHQL
end
test "user_create" do
variables = {
name: "Test User",
email: "test@hipcamp.com",
}
post graphql_path, params: { query: @query, variables: variables }
user = User.last
assert_equal(
{
"data" => {
"userCreate" => {
"user" => {
"id" => user.id.to_s,
"name" => user.name,
"email" => user.email,
"createdAt" => user.created_at.iso8601,
"updatedAt" => user.updated_at.iso8601,
},
},
},
},
@response.parsed_body
)
assert_equal(user.name, "Test User")
assert_equal(user.email, "test@hipcamp.com")
end
test "user_create with errors" do
variables = {
name: "Test User",
email: users(:daniel).email,
}
post graphql_path, params: { query: @query, variables: variables }
assert_equal(
{
"data" => {
"userCreate" => nil,
},
"errors" => [
{
"message" => "User invalid",
"locations" => [{ "line" => 2, "column" => 3 }],
"path" => ["userCreate"],
"extensions" => { "email" => ["has already been taken"] },
},
],
},
@response.parsed_body
)
end
end
These tests should look similar to the last set we wrote, with a few exceptions. Here we’re performing a mutation instead of a query and the error case is different, but quite familiar.
Run the new tests.
Terminal
bin/rails t test/integration/types/mutation_type/user_create_test.rb
Check for regressions by running all tests.
Terminal
bin/rails t
Success!
âś… Make a commit
âś… As an app user, I want to create a new user, so that that new engineers are available to host All Hands.
Next: Chapter 6 - The Round