Isolating and recycling logic with Mutations
Tags: ruby, beginners, graphql, rails
Published: 2020-03-03 13:03:50

As I introduced last week [in my first article](https://dev.to/tymate/first-dive-into-graphql-ruby-nak), I'm working on an API that provides not only REST services but also recently a GraphQL endpoint.

As part of this, I had to create `queries` repeating exactly the logic already present in some of our REST controllers for desktop applications designed entirely in GraphQL. There was a high risk of unnecessary code duplication.

## Mutations (as opposed to Mutations)

Fortunately, my colleagues had already introduced the solution to this problem by lightening our controllers through the use of [Mutations](https://github.com/cypriss/mutations) gem.

> 🤯 Yes, the name can make conversations tricky: difficult to make a clear sentence when talking about the mutation (of the gem) used inside a mutation (graphql).

At its core, Mutations allows you to create classes that manage :

- type checking of incoming parameters
- data validation
- customizable error messages

## Lighten its controllers by isolating the business logic

Our initial use of gem was just to, as I said, lighten our controllers. For example, a controller like this:

```ruby
# app/controllers/v1/providers_controller.rb

# ===================================================================
# === BEFORE REFACTORING ============================================
# ===================================================================

module V1
  class ProvidersController < ApiController

    def create
      provider = Provider.new(permitted_params)

      check_phone = Phonelib.valid?(
        Phonelib.parse(permitted_params[:phone_number]).e164
      )
      provider.errors.add(:phone_number, :invalid) unless check_phone

      iban_errors = IBANTools::IBAN.new(iban).validation_errors
      iban_errors.each { |e| provider.errors.add(:iban, e) }
      
      if provider.save
        render json: provider
      else
        errors_json(provider.errors.symbolic, 422)
      end
    end

    private

    def permitted_params
      params.require(:provider)
            .permit(
              :display_name,
              :siret,
              :phone_number,
              :email,
              :iban,
              :bic
            )
    end
  end
end

# ====================================================================
# ==== AFTER REFACTORING =============================================
# ====================================================================

module V1
  class ProvidersController < ApiController

    def create
      outcome = ::Provider::CreateMutation.run(permitted_params)
      respond_with_mutation outcome,
                            serializer: ProviderSerializer
    end

    private

    def permitted_params
      params.require(:provider)
            .permit(
              :display_name,
              :siret,
              :phone_number,
              :email,
              :iban,
              :bic
            )
    end
  end
end
```

By rewriting our controllers this way, we save ourselves the need to repeat `if something.save else...` each time, using the `respond_with_mutation` method we previously defined in `ApiController`.

```ruby
# app/controllers/api_controller.rb

class ApiController < ActionController::API
  # ...

  def respond_with_mutation(outcome, serializer_opts = {})
    if outcome.success?
      render serializer_opts.merge(json: outcome.result)
    else
      errors_json(outcome.errors.symbolic, 422)
    end
  end

  # ...
end
```

Besides, as I was explaining, we gain a type check on the incoming parameters, which was not at all initially managed, and a clearer definition of the validations required before the record is created.

All this happens in the `Mutation` which is defined as follows:

```ruby
# app/mutations/provider/create_mutation.rb

class Provider::CreateMutation < BaseMutation
  required do
    string :display_name
    string :siret
    string :phone_number
    string :email
    string :iban
    string :bic
  end
  
  # Optional inputs can be defined:
  # 
  # optional do
  #   string :something
  #   integer :something_else
  #   datetime :lunch_time
  # end

  def validate
    check_phone = Phonelib.valid?(Phonelib.parse(phone_number).e164)
    add_error(:phone_number, :invalid) unless check_phone

    iban_errors = IBANTools::IBAN.new(iban).validation_errors
    iban_errors.each { |e| add_error(:iban, e) }
  end

  def execute
    provider = Provider.new(inputs)
    provider.save!
    provider
  end
end
```

The slightest error, whether in `validate`, `execute`, or directly in the format of the input arguments interrupts the `Mutation`. The `.success?` method will then return `false`, and the details of the errors will be listed in `outcome.errors`.

## Reusing the logic in another part of the API

The consistent use of `Mutations` to isolate the business logic saved me a lot of time when I started integrating GraphQL Ruby into our API.

Instead of having to duplicate this logic, I just had to call these existing classes in my GraphQL mutations. To do this, I only had to redefine within GraphQL the `respond_with_mutation` method we saw above.

```ruby
# app/graphql/mutations/base_mutation.rb

class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation
  # ...

  def respond_with_mutation(outcome)
    if outcome.success?
      { outcome.result.class.name.underscore => outcome.result }
    else
      errors = outcome.errors.symbolic.map do |k, v|
        parse_error_hash(k, v)
      end.flatten
      { errors: errors }
    end
  end

  def parse_error_hash(args)
    # crappy code which need clear refactor that transform Mutations errors
    # into a Types::MutationErrorType compliant hash
  end
 
  # ...
end
```

This one is slightly heavier than the one defined in `ApiController`, simply because I need to make sure the returned errors match the `MutationErrorType` format I have defined in my code.

Once this method is defined, just call it:

```ruby
# app/graphql/mutations/create_provider.rb

module Mutations
  class CreateProvider < Mutations::BaseMutation

    argument :display_name, String, required: true
    argument :phone_number, String, required: true
    argument :siret, String, required: true
    argument :email, String, required: true
    argument :iban, String, required: true
    argument :bic, String, required: true

    field :provider, Types::ProviderType, null: true
    field :errors, [Types::MutationErrorType], null: true

    def resolve(**args)
      outcome = ::Provider::CreateMutation.run(args)
      respond_with_mutation(outcome)
    end
  end
end
```

Thus, I can expose on my API a REST endpoint and a GraphQL mutation that offer the same action, without having to maintain twice the same logic in two different places!

## Now what?

The use of `Mutations` allowed me to acquire better reflexes as a developer. Before I started using GraphQL, it was my first approach to type checking, Ruby being my first programming language. It also led me to pay more attention to my validations, to identify the ones that were missing in my models, and so on.

Maybe it's just because my CTO happens to be an evangelist and I attended an inspiring presentation from [@nathalyvillamor](https://twitter.com/nathalyvillamor) to [@parisrb](hhttps://twitter.com/parisrb) on the subject, but I feel like I've been hearing a lot about `Monads` lately. Although this is a new concept for me, my first reads of the [dry-monads](https://dry-rb.org/gems/dry-monads/1.3/) documentation reminded me of `Mutations`. So I wouldn't be surprised if more experienced developers advise me to look this way to go further.

In any case, I highly encourage beginners like me to look at one or the other, as these paradigms make the design of our APIs much more comfortable.