Custom Connections

GraphQL-Ruby ships with built-in connection support for ActiveRecord, Sequel, Mongoid, and Ruby Arrays. You can read more in the Using Connections guide.

When you want to serve a connection based on your own data object, you can create a custom connection. The implementation will have several components:

For this example, we’ll imagine that your application communicates with an external search engine, and expresses all search results with a SearchEngine::Result class. (There isn’t really any class like this; it’s an arbitrary example of an application-specific collection of items.)

Application Object

In Ruby, everything is an object, and that includes lists of objects. For example, we think of an Array as a list of objects, but Arrays are also objects in their own right.

Some list objects have very fancy implementations. Think of an ActiveRecord::Relation: it gathers up the parts of a SQL query, and at the right moment, it dispatches a call to your database to fetch the objects in the list. An ActiveRecord::Relation is also a list object.

Your application probably has other list objects that you want to paginate via GraphQL connections. For example, you might show a user some search results, or a list of files from a fileserver. Those lists are modeled with list objects, and those list objects can be wrapped with connection wrappers.

Connection Wrapper

A connection wrapper is an adapter between a plain-Ruby list object (like an Array, Relation, or something application-specific, like SearchEngine::Result) and a GraphQL connection type. The connection wrapper implements methods which the GraphQL connection type requires, and it implements those methods based on the underlying list object.

You can extend GraphQL::Pagination::Connection to get started on a custom connection wrapper, for example:

# app/graphql/connections/search_results_connection.rb
class Connections::SearchResultsConnection < GraphQL::Pagination::Connection
  # implementation here ...
end

The methods you must implement are:

How to implement these methods (efficiently!) depends on your backend and how you communicate with it. For inspiration, you can see the built-in connections:

Using a Custom Connection

To integrate your custom connection wrapper with GraphQL, you have two options:

The first case is very convenient, and the second case makes it possible to customize connections for specific situations.

To map the wrapper to a class of objects, add it to your schema:

class MySchema < GraphQL::Schema
  # Add the connection plugin
  use GraphQL::Pagination::Connections
  # Hook up a custom wrapper
  connections.add(SearchEngine::Result, Connections::SearchResultsConnection)
end

Now, any time a field returns an instance of SearchEngine::Result, it will be wrapped with Connections::SearchResultsConnection

Alternatively, you can apply a connection wrapper on a case-by-case basis by applying it during the resolver (method or GraphQL::Schema::Resolver):

field :search, Types::SearchResult.connection_type, null: false do
  argument :query, String, required: true
end

def search(query:)
  search = SearchEngine::Search.new(query: query, viewer: context[:current_user])
  results = search.results
  # Apply the connection wrapper and return it
  Connections::SearchResultsConnection.new(results)
end

GraphQL-Ruby will use the provided connection wrapper in that case. You can use this fine-grained approach to handle special cases or implement performance optimizations.

Connection Type

Connection types are GraphQL object types which comply to the Relay connection specification. GraphQL-Ruby ships with some tools to help you create those object types:

For example, you could implement a base connection class:

class Types::BaseConnectionObject < Types::BaseObject
  # implement based on `GraphQL::Types::Relay::BaseConnection`, etc
end

Then hook it up to your base classes:

class Types::BaseObject < GraphQL::Schema::Object
  # ...
  connection_type_class(Types::BaseConnectionObject)
end

class Types::BaseUnion < GraphQL::Schema::Union
  connection_type_class(Types::BaseConnectionObject)
end

module Types::BaseInterface
  include GraphQL::Schema::Interface

  connection_type_class(Types::BaseConnectionObject)
end

Then, when defining fields, you could use .connection_type to use your connection class hierarchy:

field :posts, Types::Post.connection_type, null: false

(Those fields get connection: true by default, because the generated connection type’s name ends in *Connection.)