🐈

graphql-rubyでcursorページネーションをしながら現在のページと全体の件数を返す

2023/08/07に公開

はじめに

普段は個人投資家として投資を行いながら
複数のスタートアップで開発の支援をしたり、リードエンジニアをしています

https://twitter.com/0000_pg

カーソルページネーション

GraphQLではカーソルを使ってページネーションを実装することがほとんどだと思います

ex.

{
  products(first: 10, after: "Mw") {
    edges {
      node {
        id
        name
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

実装のなかで、フロント側で

  • 全体の件数を知りたい
  • 現在のページ数が知りたい

という要件が発生するときの実装方法についてです

実際のコード

ConnectionTypeの細かい部分は異なるので、一例です

# frozen_string_literal: true

module Types
  class BaseConnection < Types::BaseObject
    include GraphQL::Types::Relay::ConnectionBehaviors

    node_nullable(false)
    edges_nullable(false)
    edge_nullable(false)

    field :total_count, Integer, null: false
    field :current_page, Integer, null: false

    def total_count
      object.items.length
    end

    def current_page
      (offset / page_size) + 1
    end

    private

      def offset
        # 空配列を返す場合にcursorがnilになり、urlsafe_decode64でエラーになるのを防ぐ
        return 0 if object.edges.first&.cursor.nil?
	
        Base64.urlsafe_decode64(object.edges.first&.cursor)&.to_i
      end

      def page_size
        object.arguments[:first].to_i
      end
  end
end

解説

total_count

このfieldで全体の件数を返しています
object.items でActiveRecordのオブジェクトを取得できます

object.nodes でも取得できますが、edgeオブジェクトに変換されるので .itemsでいいかと

公式の実装

https://graphql-ruby.org/type_definitions/extensions#customizing-connections

class Types::MyCustomConnection < GraphQL::Types::Relay::BaseConnection
  # BaseConnection has these nullable configurations
  # and the nodes field by default, but you can change
  # these options if you want
  edges_nullable(true)
  edge_nullable(true)
  node_nullable(true)
  has_nodes_field(true)

  field :total_count, Integer, null: false

  def total_count
    object.items.size
  end
end

.lengthを使っている理由

これは実装によりけりですが、ActiveRecordで .group などをした結果を返している場合
.size.count を使うとグルーピングされたオブジェクトを返してしまうので
graphql/types/int.rb:24value = value.to_i でエラーになります

https://github.com/rmosolgo/graphql-ruby/blob/9844fb4e233773b787cf9874c1cbbde663aa3911/lib/graphql/types/int.rb#L24

なのでメモリにロードされているオブジェクトを返す .length を使っています

current_page

このfieldでいま何ページ目にいるのかを返しています

最初のnodeのcursorをデコードして、数値化しています
表示件数を知らないといけないので引数として送られてくる first の値を取得しています

Discussion