Money Forward Developers Blog

株式会社マネーフォワード公式開発者向けブログです。技術や開発手法、イベント登壇などを発信します。サービスに関するご質問は、各サービス窓口までご連絡ください。

20230215130734

【graphql-ruby】RelayConnectionのPageInfoに任意のfieldを追加する

こんにちは、クラウド債務支払でバックエンドエンジニアをしている@いいねです。

graphql-rubyを採用しているプロダクトで、ページネーションを実装したいというケースはよくある話だと思います。 graphql-rubyではRelayConnectionを用いたページネーション機能が提供されており、開発者はこれを使って簡単に実装することができます。

しかし、標準で用意されているPageInfo型では、ページネーションのコンテキストにおいて必要な全ての情報を提供していない場合があります。
例えば、クライアントがリストの総アイテム数を知りたいと思った場合PageInfoにはその情報が含まれていません。

今回PageInfo型にfieldを拡張する方法を考えたので紹介します。

1. CustomPageInfoTypeの定義

まずはGraphQL::Types::Relay::PageInfoを継承したClassを定義し、追加したいfield定義と処理するためのメソッドを実装します。
この例ではnodeの総数を返すtotalCountfieldを追加しました。

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を返すようにpageInfofieldを上書きします。

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に組み込む

最後にBaseObjectClassにてconnection_type_classCustomConnectionTypeを使用するように設定します。

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としてのインターフェースを持っていますが、pageInfofieldでCustomPageInfoを返すようになります。
また、CustomPageInfoは従来のPageInfoのfieldを備えつつ、新しく追加したtotalCountfieldを持っています。

まとめ

この記事ではgraphql-rubyに用意されているRelayConnectionを拡張する方法を紹介しました。
graphql-rubyでは継承を用いることで、簡単に特定のオブジェクトにカスタムフィールドを追加することができました。
クライアントサイドと協働し、ニーズに合わせて適切なデータを提供することでユーザー体験を向上させましょう。


マネーフォワード福岡拠点ではGraphQLに興味のあるエンジニアを募集しています!

hrmos.co

福岡開発拠点のサイトもあるのでぜひみてください!

fukuoka.moneyforward.com