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!
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 :)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With