Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GraphQL Ruby: How to write DRY mutations with multiple parameters?

I'm writing a devise-jwt-based authentication system for my graphql-ruby using app. In the process, I've made a mutation for creating a new user account, which takes 7 parameters, which creates quite a lot of repetition in my code:

module Mutations
  class SignUpMutation < Mutations::BaseMutation
    argument :email, String, required: true
    argument :password, String, required: true
    argument :family_name, String, required: true
    argument :family_name_phonetic, String, required: true
    argument :given_name, String, required: true
    argument :given_name_phonetic, String, required: true
    argument :newsletter_optin, Boolean, required: false

    field :token, String, null: true
    field :user, Types::UserType, null: true

    def resolve(email:, password:,
                family_name:, family_name_phonetic:,
                given_name:, given_name_phonetic:,
                newsletter_optin:
               )
      result = {
        token: nil,
        user: nil
      }
      new_user = User.new(
        email: email,
        password: password,
        family_name: family_name,
        family_name_phonetic: family_name_phonetic,
        given_name: given_name,
        given_name_phonetic: given_name_phonetic,
        newsletter_optin: newsletter_optin
      )
      if new_user.save!
        result[:token] = new_user.token
        result[:user] = new_user
      end
      result
    end
  end
end

How could I DRY this up to avoid repeating the names of the mutation arguments all over the place?

Thank you in advance!

like image 603
gueorgui Avatar asked Apr 12 '26 08:04

gueorgui


2 Answers

Answering my own question. The correct way to not have to deal with so many parameters is to use Input Objects instead of separate parameters. From the graphql-ruby documentation:

Input object types are complex inputs for GraphQL operations. They’re great for fields that need a lot of structured input, like mutations or search fields.

So I've defined my Input Object as such:

module Types
  class UserAttributes < Types::BaseInputObject
    description 'Attributes for creating or updating a user'
    argument :email, String, required: true
    argument :password, String, required: true
    argument :family_name, String, required: true
    argument :family_name_phonetic, String, required: true
    argument :given_name, String, required: true
    argument :given_name_phonetic, String, required: true
    argument :newsletter_optin, Boolean, required: false
  end
end

and then refactored my mutation like this:

module Mutations
  class SignUpMutation < Mutations::BaseMutation
    argument :attributes, Types::UserAttributes, required: true

    field :token, String, null: true
    field :user, Types::UserType, null: true

    def resolve(attributes:)
      result = {
        token: nil,
        user: nil
      }
      new_user = User.new(attributes.to_hash)
      if new_user.save!
        result[:token] = new_user.token
        result[:user] = new_user
      end
      result
    end
  end
end

Finally, this code feels more ruby-like :)

like image 128
gueorgui Avatar answered Apr 15 '26 00:04

gueorgui


If you'd like, you could do something like this:

[
  :email,
  :password,
  :family_name,
  :family_name_phonetic,
  :given_name,
  :given_name_phonetic
].each do |arg|
  argument arg, String, required: true
end

You might think any more than this is overkill, but Ruby is very flexible. If you really wanted to, you could even do something like

def resolve(email:, password:,
            family_name:, family_name_phonetic:,
            given_name:, given_name_phonetic:,
            newsletter_optin:)
  result = {
    token: nil,
    user: nil
  }
  params = method(__method__).parameters.map(&:last)
  opts = params.map{|p| [p, eval(p.to_s)]}.to_h
  new_user = User.new(opts)
  if new_user.save!
    result[:token] = new_user.token
    result[:user] = new_user
  end
  result
end

You can see this answer for an explanation

If you wanted even more than this, you could use a more detailed field list, and define_method - you could get it all the way to the point where you only type e.g. :email once.

Would that be better? Maybe, if you've got hundreds of these to do. Or if you want to start defining things at runtime.

like image 45
iCodeSometime Avatar answered Apr 15 '26 01:04

iCodeSometime