Graphql-ruby: (Question/Feature Request) Is there a good way to override `graphql_name` methods

Created on 16 Apr 2020  路  2Comments  路  Source: rmosolgo/graphql-ruby

Is your feature request related to a problem? Please describe.

I am restructuring the folder and aiming to reduce indirection.
The folder structure I want to achieve:

- graphql
  - base_types
    - base_argument.rb
    - base_enum.rb
    - base_field.rb
    ... 
  - view_models
    - failure.rb
    - success.rb
    - error.rb
  - mutations
  schema.rb

In models/failure.rb

module ViewModels
  class Failure
    class Type < BaseTypes::BaseObject
      field :accountable, String, null: false
      field :errors, [ViewModels::Error], null: false
    end

    attr_reader :errors

    def initialize(errors:)
      @errors = errors
    end

    def accountable
      'CLIENT'
    end
  end
end

In models/success.rb

module ViewModels
  module Success
    module Interface
      include BaseTypes::BaseInterface
      ...
    end
  end
end

This is essentially a lazy way to have type ViewModels::Failure::Type and implementation ViewModels::Failure defined closely in the same file, meanwhile it's Rails 6 Zeitwerk autoloader compatible.

However, I cannot find a easy way to override the default graphql type name generator, because graphql_name and default_graphql_name are used in some inconsistent ways across different components. So far, I am only able to override GraphQL::Schema::Object.self.graphql_name. If I monkey patch GraphQL::Schema::Member::BaseDSLMethods.graphql_name, things like Directive will break. And for Interfaces, I couldn't get it to work at all.

So my first question, is there any existing way designed to achieve type name customization?

Describe the solution you'd like

If no, I'd love to have an easy way to properly configure type naming.

Some common pattern such as

  1. in config/initializers/grpahql_ruby.rb
GraphQL.configure do |config| 
  config.graphql_name = lambda (provided_name) 
    name_parts = name.split('::')
    selection = name_parts.size > 1 ?  name_parts[-2..-1] :  [name_parts.last]
    selection.join.sub(/Type\Z/, '')
  end
end
  1. in graphql/application_schema.rb
class ApplicationSchema < GraphQL::Schema
  def self.graphql_name(provided_name)
    ...
  end
end

Describe alternatives you've considered

If it's going to take huge efforts and delay to achieve what I mentioned, would you plz give me at least some examples/guidelines/direction on how customization is currently possible for interfaces?

Thank heaps. You guys have done a great job on designing and maintaining the library 馃檪

Most helpful comment

override GraphQL::Schema::Object.self.graphql_name

You're on the right track there! The catch is that Interfaces are a bit wacky because they're modules -- it takes some tricks to get them to properly "inherit" class methods. The default_graphql_name override has to be in the definition_methods do ... end block:

https://graphql-ruby.org/type_definitions/interfaces.html#definition-methods

Altogether, here's an example of overriding default_graphql_name on every base type, using a shared module:

require "bundler/inline"

gemfile do
  source "https://rubygems.org"
  gem "graphql", "1.10.6"
end

module CustomGraphQLName
  def default_graphql_name
    super + "CustomName"
  end
end

class BaseObject < GraphQL::Schema::Object
  extend CustomGraphQLName
end

module BaseInterface
  include GraphQL::Schema::Interface
  definition_methods do
    include CustomGraphQLName
  end
end

class BaseScalar < GraphQL::Schema::Scalar
  extend CustomGraphQLName
end

class BaseUnion < GraphQL::Schema::Union
  extend CustomGraphQLName
end

class BaseEnum < GraphQL::Schema::Enum
  extend CustomGraphQLName
end

class BaseInputObject < GraphQL::Schema::InputObject
  extend CustomGraphQLName
end

class UUID < BaseScalar
end

module Node
  include BaseInterface

  field :id, UUID, null: false
end


class Thing < BaseObject; implements Node; end
class OtherThing < BaseObject; implements Node; end
class Stuff < BaseUnion
  possible_types Thing, OtherThing
end

class Setting < BaseEnum
  value "A"
  value "B"
end

class Options < BaseInputObject
  argument :opt1, Boolean, required: false
end

class Query < BaseObject
  implements Node

  field :stuff, Stuff, null: true do
    argument :enum, Setting, required: false
    argument :input_obj, Options, required: false
  end
end

class Schema < GraphQL::Schema
  query Query
end

puts Schema.to_definition

It outputs a schema were ever type name has been customized:

schema {
  query: QueryCustomName
}

interface NodeCustomName {
  id: UUIDCustomName!
}

input OptionsCustomName {
  opt1: Boolean
}

type OtherThingCustomName implements NodeCustomName {
  id: UUIDCustomName!
}

type QueryCustomName implements NodeCustomName {
  id: UUIDCustomName!
  stuff(enum: SettingCustomName, inputObj: OptionsCustomName): StuffCustomName
}

enum SettingCustomName {
  A
  B
}

union StuffCustomName = OtherThingCustomName | ThingCustomName

type ThingCustomName implements NodeCustomName {
  id: UUIDCustomName!
}

scalar UUIDCustomName

I think a solution like that should work in your case too, feel free to follow up here if you run into any trouble with it!

All 2 comments

override GraphQL::Schema::Object.self.graphql_name

You're on the right track there! The catch is that Interfaces are a bit wacky because they're modules -- it takes some tricks to get them to properly "inherit" class methods. The default_graphql_name override has to be in the definition_methods do ... end block:

https://graphql-ruby.org/type_definitions/interfaces.html#definition-methods

Altogether, here's an example of overriding default_graphql_name on every base type, using a shared module:

require "bundler/inline"

gemfile do
  source "https://rubygems.org"
  gem "graphql", "1.10.6"
end

module CustomGraphQLName
  def default_graphql_name
    super + "CustomName"
  end
end

class BaseObject < GraphQL::Schema::Object
  extend CustomGraphQLName
end

module BaseInterface
  include GraphQL::Schema::Interface
  definition_methods do
    include CustomGraphQLName
  end
end

class BaseScalar < GraphQL::Schema::Scalar
  extend CustomGraphQLName
end

class BaseUnion < GraphQL::Schema::Union
  extend CustomGraphQLName
end

class BaseEnum < GraphQL::Schema::Enum
  extend CustomGraphQLName
end

class BaseInputObject < GraphQL::Schema::InputObject
  extend CustomGraphQLName
end

class UUID < BaseScalar
end

module Node
  include BaseInterface

  field :id, UUID, null: false
end


class Thing < BaseObject; implements Node; end
class OtherThing < BaseObject; implements Node; end
class Stuff < BaseUnion
  possible_types Thing, OtherThing
end

class Setting < BaseEnum
  value "A"
  value "B"
end

class Options < BaseInputObject
  argument :opt1, Boolean, required: false
end

class Query < BaseObject
  implements Node

  field :stuff, Stuff, null: true do
    argument :enum, Setting, required: false
    argument :input_obj, Options, required: false
  end
end

class Schema < GraphQL::Schema
  query Query
end

puts Schema.to_definition

It outputs a schema were ever type name has been customized:

schema {
  query: QueryCustomName
}

interface NodeCustomName {
  id: UUIDCustomName!
}

input OptionsCustomName {
  opt1: Boolean
}

type OtherThingCustomName implements NodeCustomName {
  id: UUIDCustomName!
}

type QueryCustomName implements NodeCustomName {
  id: UUIDCustomName!
  stuff(enum: SettingCustomName, inputObj: OptionsCustomName): StuffCustomName
}

enum SettingCustomName {
  A
  B
}

union StuffCustomName = OtherThingCustomName | ThingCustomName

type ThingCustomName implements NodeCustomName {
  id: UUIDCustomName!
}

scalar UUIDCustomName

I think a solution like that should work in your case too, feel free to follow up here if you run into any trouble with it!

This, is, so, elegant.
And thanks for teaching me puts Schema.to_definition馃憤

Was this page helpful?
0 / 5 - 0 ratings