こんにちは、クラウド債務支払でバックエンドエンジニアをしている@いいねです。
graphql-rubyを採用しているプロダクトで、ページネーションを実装したいというケースはよくある話だと思います。 graphql-rubyではRelayConnectionを用いたページネーション機能が提供されており、開発者はこれを使って簡単に実装することができます。
しかし、標準で用意されているPageInfo
型では、ページネーションのコンテキストにおいて必要な全ての情報を提供していない場合があります。
例えば、クライアントがリストの総アイテム数を知りたいと思った場合PageInfo
にはその情報が含まれていません。
今回PageInfo
型にfieldを拡張する方法を考えたので紹介します。
1. CustomPageInfoTypeの定義
まずはGraphQL::Types::Relay::PageInfo
を継承したClassを定義し、追加したいfield定義と処理するためのメソッドを実装します。
この例ではnodeの総数を返すtotalCount
fieldを追加しました。
class Types::CustomPageInfoType < GraphQL::Types::Relay::PageInfo field :total_count, Int, null: false, description: 'Total nodes count' def total_count object.nodes.size end end
2. CustomConnectionTypeの定義
次にConnectionを定義していきます。
はじめに、Connectionとして振る舞うClassBaseConnection
を定義します。
GraphQL::Types::Relay::ConnectionBehaviors
をincludeすることでConnectionとして振る舞うClassとなります。
module Types class BaseConnection < Types::BaseObject include GraphQL::Types::Relay::ConnectionBehaviors end end
続けて、BaseConnection
を継承したCustomConnectionType
を定義し、作成したCustomPageInfoType
を返すようにpageInfo
fieldを上書きします。
module Types class CustomConnectionType < Types::BaseConnection # CustomPageInfoTypeを使用するようにpage_infoフィールドを上書き field :page_info, Types::CustomPageInfoType, null: false, description: 'Information to aid in pagination.' end end
graphql-rubyでは同一のClassに重複したfield定義を行うことができないため、このような方法でConnectionを拡張します。
3. BaseObjectに組み込む
最後にBaseObject
Classにてconnection_type_class
でCustomConnectionType
を使用するように設定します。
module Types class BaseObject < GraphQL::Schema::Object connection_type_class(Types::CustomConnectionType) # CustomConnectionクラスを指定 end end
RelayConnectionを返すfieldの定義は普段通りTypeのクラス名.connection_type
で構いません。
ライブラリがTypes::CustomConnectionType
を用いてConnectionの定義を行ってくれるようになります。
field :items, Types::ItemType.connection_type, null: false
特定のTypeのConnectionのみ変更したい場合は、BaseObject
ではなくそのTypeのClassでのみconnection_type_class
を設定することもできます。
module Types class UserType < Types::BaseObject connection_type_class(Types::CustomConnectionType) # CustomConnectionクラスを指定 end end
出力されるスキーマ
出力されるスキーマは以下のようになります。
type User { items( """ Returns the elements in the list that come after the specified cursor. """ after: String """ Returns the elements in the list that come before the specified cursor. """ before: String """ Returns the first _n_ elements from the list. """ first: Int """ Returns the last _n_ elements from the list. """ last: Int ): ItemConnection! } type ItemConnection { """ A list of edges. """ edges: [ItemEdge!]! """ A list of nodes. """ nodes: [Item!]! """ Information to aid in pagination. """ pageInfo: CustomPageInfo! } """ Information about pagination in a connection. """ type CustomPageInfo { """ When paginating forwards, the cursor to continue. """ endCursor: String """ When paginating forwards, are there more items? """ hasNextPage: Boolean! """ When paginating backwards, are there more items? """ hasPreviousPage: Boolean! """ When paginating backwards, the cursor to continue. """ startCursor: String """ Total nodes count """ totalCount: Int! }
items
はRelayConnectionとしてのインターフェースを持っていますが、pageInfo
fieldでCustomPageInfo
を返すようになります。
また、CustomPageInfo
は従来のPageInfo
のfieldを備えつつ、新しく追加したtotalCount
fieldを持っています。
まとめ
この記事ではgraphql-rubyに用意されているRelayConnectionを拡張する方法を紹介しました。
graphql-rubyでは継承を用いることで、簡単に特定のオブジェクトにカスタムフィールドを追加することができました。
クライアントサイドと協働し、ニーズに合わせて適切なデータを提供することでユーザー体験を向上させましょう。
マネーフォワード福岡拠点ではGraphQLに興味のあるエンジニアを募集しています!
福岡開発拠点のサイトもあるのでぜひみてください!