🐈
graphql-rubyでcursorページネーションをしながら現在のページと全体の件数を返す
はじめに
普段は個人投資家として投資を行いながら
複数のスタートアップで開発の支援をしたり、リードエンジニアをしています
カーソルページネーション
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
でいいかと
公式の実装
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:24
の value = value.to_i
でエラーになります
なのでメモリにロードされているオブジェクトを返す .length
を使っています
current_page
このfieldでいま何ページ目にいるのかを返しています
最初のnodeのcursorをデコードして、数値化しています
表示件数を知らないといけないので引数として送られてくる first
の値を取得しています
Discussion