🐈
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