Extending the GraphQL-Ruby Type Definition System
While integrating GraphQL into your app, you can customize the definition DSL. For example, you might:
-
Assign “area of responsibility” to different types and fields
-
DRY up shared logic between types and fields
-
Attach metadata for use during authorization
This guide describes various options for extending the class-based definition API. Keep in mind that these approaches may change as the API matures. If you’re having trouble, consider opening an issue on GitHub to get help.
Note: This document describes best practice with GraphQL-Ruby 1.10+. For customizing schemas on older versions, use GitHub to browse older versions of this page.
Customization Overview
In general, the schema definition process goes like this:
-
The application defines lots of classes for the
GraphQLtypes -
Starting from root types (
query,mutation, andsubscription) and any definedorphan_types, the schema discovers all types, fields, arguments, enum values, and directives in the schema -
Non-type objects (fields, arguments, enum values) are initialized when they’re attached to the classes or instances they belong to.
Customizing type definitions
In your custom classes, you can add class-level instance variables that hold configuration. For example:
class Types::BaseObject < GraphQL::Schema::Object # Call this method in an Object class to get or set the permission level: def self.required_permission(permission_level = nil) if permission_level.nil? # return the configured value @required_permission else @required_permission = permission_level end end end # Then, in concrete classes class Dossier < BaseObject # The Dossier object type will have `.metadata[:required_permission] # => :admin` required_permission :admin end # Now, the type responds to that method: Dossier.required_permission # => :admin
Now, any runtime code which calls type.required_permission will get the configured value.
Customizing fields
Fields are generated in a different way. Instead of using classes, they are generated with instances of GraphQL::Schema::Field (or a subclass). In short, the definition process works like this:
# This is what happens under the hood, roughly: # In an object class: field :name, String, null: false # ... # Leads to: field_config = GraphQL::Schema::Field.new(name: :name, type: String, null: false)
So, you can customize this process by:
-
creating a custom class which extends
GraphQL::Schema::Field -
overriding
#initializeon that class (instance methods) -
registering that class as the
field_classon Objects and Interfaces which should use the customized field.
For example, you can create a custom class which accepts a new parameter to initialize:
class Types::BaseField < GraphQL::Schema::Field # Override #initialize to take a new argument: def initialize(*args, required_permission: nil, **kwargs, &block) @required_permission = required_permission # Pass on the default args: super(*args, **kwargs, &block) end attr_reader :required_permission end
Then, pass the field class as field_class(...) wherever it should be used:
class Types::BaseObject < GraphQL::Schema::Object # Use this class for defining fields field_class BaseField end # And.... class Types::BaseInterface < GraphQL::Schema::Interface field_class BaseField end class Mutations::BaseMutation < GraphQL::Schema::RelayClassicMutation field_class BaseField end
Now, BaseField.new(*args, &block) will be used to create GraphQL::Schema::Fields on those types. At runtime field.required_permission will return the configured value.
Customizing Connections
Connections may be customized in a similar way to Fields.
-
Create a new class extending ‘GraphQL::Types::Relay::BaseConnection’
-
Assign it to your object/interface type with
connection_type_class(MyCustomConnection)
For example, you can create a custom connection:
class Types::MyCustomConnection < GraphQL::Types::Relay::BaseConnection # BaseConnection has these nullable configurations # and the nodes field by default, but you can change # these options if you want edges_nullable(true) edge_nullable(true) node_nullable(true) has_nodes_field(true) field :total_count, Integer, null: false def total_count object.items.size end end
Then, pass the field class as connection_type_class(...) wherever it should be used:
module Types class Types::BaseObject < GraphQL::Schema::Object # Use this class for defining connections connection_type_class MyCustomConnection end end
Now, all type classes that extend BaseObject will have a connection_type with the additional field totalCount.
Customizing Edges
Edges may be customized in a similar way to Connections.
-
Create a new class extending ‘GraphQL::Types::Relay::BaseEdge’
-
Assign it to your object/interface type with
edge_type_class(MyCustomEdge)
Customizing Arguments
Arguments may be customized in a similar way to Fields.
-
Create a new class extending
GraphQL::Schema::Argument -
Use
argument_class(MyArgClass)to assign it to your base field class, base resolver class, and base mutation class
Then, in your custom argument class, you can use #initialize(name, type, desc = nil, **kwargs) to take input from the DSL.
Customizing Enum Values
Enum values may be customized in a similar way to Fields.
-
Create a new class extending
GraphQL::Schema::EnumValue -
Assign it to your base
Enumclass withenum_value_class(MyEnumValueClass)
Then, in your custom enum class, you can use #initialize(name, desc = nil, **kwargs) to take input from the DSL.