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 @items based on the given arguments#has_next_page, which returns true if there are items after the ones in #nodes#has_previous_page, which returns true if there are items before the ones in #nodes#cursor_for(item), which returns a String to serve as the cursor for itemHow to implement these methods (efficiently!) depends on your backend and how you communicate with it. For inspiration, you can see the built-in connections:
GraphQL::Pagination::ArrayConnectionGraphQL::Pagination::ActiveRecordRelationConnectionGraphQL::Pagination::SequelDatasetConnectionGraphQL::Pagination::MongoidRelationConnectionTo 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 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 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::BaseConnection and GraphQL::Types::Relay::BaseEdge are 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_type which 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 *Connection.)