Skip to the content.

Home

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