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.)
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.
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 returns
trueif there are items after the ones in
#has_previous_page, which returns
trueif there are items before the ones in
#cursor_for(item), which returns a String to serve as the cursor for
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:
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 # 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
Alternatively, you can apply a connection wrapper on a case-by-case basis by applying it during the resolver (method or
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 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::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.
.connection_typewhich returns a generated connection type based on that class. By default, it inherits from the provided
GraphQL::Types::Relay::BaseConnection, but you can override that by setting
connection_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