Table of Contents

Sources

Sources are a powerful feature that allows you to translate business objects that have common structures into GraphQL elements. You can think about sources as a portion of a schema that is deeply connected to another class in your ruby application. Therefore, if such a class follows a pattern, you can create a source for such a pattern and translate all similar classes to GraphQL.

Available Sources

More sources will be added, like some for direct DB connection (without Active Record).

This gem provides the following sources:

Creating a new Source

There are 4 important steps that you should follow to have a good experience with sources:

1. Create the File and set Assignment

Since sources are designed to map features of other classes, you must set the base class to which the source will translate. This ensures that the underlying class is right when resolving the type assignment. You should also set the base class of your source as abstract.

# app/graphql/sources/awesome_source.rb
module GraphQL
  class AwesomeSource < GraphQL::Source::Base
    self.abstract = true

    validate_assignment('Awesome::Base')
  end
end

Read more about abstraction.

2. Setup Hooks

Sources work with hooks. Hooks represent steps that the source may take to generate specific GraphQL components. Hooks are always applied in reverse order, meaning that the outermost class will have its hooks run first.

Hooks are ensured to run only once per build of a source. The start hook is special because it will always run before any other hook is executed. Here is the list of available hooks:

start, object, input, query, mutation, and subscription.

The order is somewhat important because using build_all will run hooks in that specific order.

You can always add your own hooks. If to change the hooks list, make sure to use a Set.

# A total rewrite
self.hook_names = %i[start object query].to_set.freeze

# A partial change
self.hook_names = hook_names.to_a.insert(1, :enums).to_set.freeze

Read more about hooks.

3. Add Shared Methods

Now you can add methods to both the construction of your GraphQL components and the resolution of fields. All class-level methods added will always be available within hooks execution. In contrast, instance-level methods are available for fields resolutions and events.

# app/graphql/sources/awesome_source.rb
step(:query) do
  safe_field("awesome_#{base_name}", :string, null: false) do
    before_resolve(:load, base_name)
  end
end

# Class-level methods should work with the class that is being translated
def self.base_name
  self.class.name.demodulize.underscore
end

# Instance-level methods describe shared process among classes
def load(name)
  assigned_class.load(name)
end

4. Inform the Settings

When you have finished your source definition, adding the base class to the list of available base sources is important. This is controlled by the sources setting. Its main effect is to support the inline definition of sources within a schema:

# app/graphql/app_schema.rb
Rails::GraphQL.config.sources << 'GraphQL::AwesomeSource'

# This will automatically identify the proper source describer and translate
# the provided class. This will create a GraphQL::Awesome::UserSource class.
source Awesome::User

Read more about inline types.

Using Sources

Once you have your source defined, you can create additional files or use the shortcut on the schema. It’s extremely important that you choose which steps you want to build so that you don’t overload your GraphQL with unnecessary objects. However, you can still choose to build them all.

# app/graphql/sources/user_source.rb
module GraphQL
  class UserSource < GraphQL::AwesomeSource
    build_all
  end
end

# OR

# app/graphql/app_schema.rb
source Awesome::User do
  build_all
end

Each hook have their respective build_{name} method.

Preventing Fields

There are several ways you can prevent specific fields from being created. If properly configured, sources should only attempt to create fields that haven’t been defined yet. Apart from that, here are other ways to prevent the creation of fields.

General

You can prevent fields from being create in any place by calling skip_fields! with the list of symbolized names.

# app/graphql/sources/user_source.rb
skip_fields! :secure_token

Per Kind

You can also prevent fields from being created per kind (where they are supposed to be added). For example, the following will prevent any :one_user and :all_users fields from being created in query fields.

# app/graphql/sources/user_source.rb
skip_from :query, :one_user, :all_users

Before Build

You can call any of the build methods with :except and :only, which works as a shortcut for the above plus the build process itself.

# app/graphql/sources/user_source.rb
build_query except: %i[one_user all_users]

Hooks

The hooks are the place where you will create the necessary elements as a result of the translation of your classes. Each hook has its own self-binding (on top of the regular binding of the class itself) and represents an area being described to GraphQL about your classes.

Managing Hooks

There are several methods implemented to set up and control the execution of hooks. Here is the list of available methods:

step(hook_name, unshift: false, &block)

This will add one more step to the given hook_name. If unshift is true, then the step will be added to the beginning of the list, making the step the last one to be executed. If you call this method after the build of that hook, the block will be executed immediately.

skip(*hook_names)

This prevents hooks from further executing. For example, if you add a skip and then a step for a given hook_name, only that last step will run, and the ones added previously will be skipped.

override(hook_name, &block)

This is just a shortcut for the skip + step combination.

disable(*hook_names)

Disable one or more hooks. This also prevents new steps from being added to those hooks.

enable(*hook_names)

Enable one or more hooks. This allows steps to be added to those hooks.

Using Hooks

Using safe_field instead of field is recommended to allow specific source classes to define fields differently what they would normally be.

When inside the step block, you should use the self-scope, the source class, and the underlying class of your source to describe it to GraphQL. You are more than welcome to use inheritance, composition, concerns, and any other object approach to execute steps.

Built-in Hooks

Here is the list of built-in hooks and their respective self-scopes.

start

This hook will always be triggered before any other hook runs. The binding is the source itself. Use this hook to require dependencies and other things from a top-level, things that are not related to GraphQL components directly.

# app/graphql/sources/awesome_source.rb
step(:start) { Awesome::Base.establish_connection }

object

This hook will set up an object that represents the underlying class. You should map all readable attributes of your class into fields in this step. The binding is the object class being defined.

# app/graphql/sources/awesome_source.rb
step(:object) do
  assigned_class.attributes.each do |attribute, type|
    safe_field(attribute, type)
  end
end

input

This hook will set up an input that represents the underlying class. You should map all writeable attributes of your class into fields in this step. The binding is the input class being defined.

# app/graphql/sources/awesome_source.rb
step(:input) do
  assigned_class.attributes.each do |attribute, type|
    safe_field(attribute, type, null: false)
  end
end

query

This hook will create one or more fields exposed to schema query operations. Usually, here you will add a read-all and read-one fields. The binding is the same as the block in query_fields from field lists.

# app/graphql/sources/awesome_source.rb
step(:query) do
  safe_field("all_#{base_name}", :string, array: true)
  safe_field("one_#{base_name}", :string)
end

If not skipped, this hook will automatically add all the source’s query fields to the schemas of the same namespace.

Proxy fields is an advanced feature. Read more about proxy fields and namespaces.

mutation

This hook is quite similar to the above. Usually, here you will add create, update, and delete fields. The binding is the same as the block in mutation_fields from field lists.

# app/graphql/sources/awesome_source.rb
step(:mutation) do
  safe_field("create_#{base_name}", :bool)
  safe_field("update_#{base_name}", :bool)
  safe_field("delete_#{base_name}", :bool)
end

Fields will also be automatically added by default.

subscription

This hook is quite similar to the above. The binding is the same as the block in subscription_fields from field lists.

# app/graphql/sources/awesome_source.rb
step(:subscription) do
  safe_field("read_#{base_name}", :string)
end

Fields will also be automatically added by default.