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:
-
The application object – the list of items that you want to paginate in
GraphQL -
The connection wrapper which wraps the application object and implements methods used by
GraphQL -
The connection type, a
GraphQLobject type which implements the connection contract
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:
-
#nodes, which returns a paginated slice of@itemsbased on the given arguments -
#has_next_page, which returnstrueif there are items after the ones in#nodes -
#has_previous_page, which returnstrueif there are items before the ones in#nodes -
#cursor_for(item), which returns a String to serve as the cursor foritem
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:
-
Map the wrapper to a list object at the schema level, so that those list objects are always automatically wrapped by your wrapper; OR
-
Use the wrapper manually in resolve methods, to override any automatic mapping
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 # 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 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:
-
GraphQL::Types::Relay::BaseConnectionandGraphQL::Types::Relay::BaseEdgeare example implementations of the spec. They don’t inherit from your application’s base object class though, so you might not be able to use them out of the box. -
Type classes respond to
.connection_typewhich returns a generated connection type based on that class. By default, it inherits from the providedGraphQL::Types::Relay::BaseConnection, but you can override that by settingconnection_type_class(Types::MyBaseConnectionObject)in your base classes.
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.)