概要
Alba(JSON シリアライザ)のレスポンスに、kaminari のページネーション情報(currentPage, totalPages 等)を meta として動的に含める方法。コレクション取得時のみ meta を付与し、単体取得時は空のhashを返す。調べた感じ、ありそうな感じでなかったので。
結論
| 要素 | 内容 |
|---|---|
| meta の定義場所 |
PostResource(各リソース)の meta ブロック |
| meta 生成ロジック |
BaseResource に共通メソッドとして定義 |
| 動的判定 |
object.is_a?(Enumerable) でコレクション/単体を判定 |
実装
BaseResource に共通メソッドを定義
# app/resources/base_resource.rb
class BaseResource
include Alba::Resource
transform_keys :lower_camel
# TODO: ここだけ、lowerCamelなのはなんとかしたい
# albaのtransform_keysでは処理されないみたい
# 普通にcontroller外でも使えるっぽい。
def paginated_meta(collection)
{
currentPage: collection.current_page,
totalPages: collection.total_pages,
totalCount: collection.total_count,
nextPage: collection.next_page,
prevPage: collection.prev_page
}
end
end
-
transform_keys :lower_camelにより、レスポンスのキーはcurrentPage,totalPages等にキャメルケース変換されないみたい - kaminari がコレクションに追加するメソッド(
current_page,total_pages等)をそのまま利用
各リソースで meta ブロックを定義
# app/resources/post_resource.rb
class PostResource < BaseResource
root_key :data, :data
# ... attributes, associations ...
meta do
if object.is_a?(Enumerable)
paginated_meta(object)
else
{}
end
end
end
-
objectは Alba が内部で保持するシリアライズ対象のオブジェクト - コレクション(
Post.page(1).per(20)の結果)の場合のみ meta を付与 - 単体(
Post.find(id))の場合は空ハッシュ → レスポンスにmeta: {}が含まれる
Controller 側
def index
posts = Post.includes(:tags, user: { avatar_attachment: :blob }, image_attachment: :blob)
.order(:id)
.page(params[:page]).per(20)
render json: PostResource.new(posts), status: :ok
end
Controller 側は特別な処理不要。kaminari でページネーションしたコレクションを渡すだけで、リソース側が自動的に meta を組み込む。
レスポンス例
{
"data": [
{ "id": 1, "body": "...", "imageUrl": "...", "tags": [...], "user": {...} }
],
"meta": {
"currentPage": 1,
"totalPages": 5,
"totalCount": 100,
"nextPage": 2,
"prevPage": null
}
}
※ ちなみに単体resourceの場合はこんな感じ。
{
"data":
{ "id": 1, "body": "...", "imageUrl": "...", "tags": [...], "user": {...} }
,
"meta": {}
}
そもそもAlbaが meta keyとかをレスポンスに組み込むまでの仕組み
deepwikiを使って、いろいろ調査した。
まず render json: HogeResource.new(@hoge) とした時に何が起きているか。
@hogeは Hogeのリソースオブジェクト、HogeResourceは自分で作ったAlbaのリソース。
この時点で railsが自動的に to_jsonを呼ぶ
But you can also render JSON passing
Alba::Resourceobject. Rails automatically callsto_jsonon a resource.
render json: FooResource.new(foo)
ここからRails がリソースオブジェクトに対して to_json を呼び出し、Alba は serialize → as_json → serializable_hash の流れでハッシュを構築し、で JSON 文字列に変換するらしい。
まず to_json。この子が呼ばれる。中身は何してるかは分からんが 89でserializeを読んでる。
serialize
このparamを見ても root_keyは arrayで渡せて、collectionか単体かで分けられるということがわかる。
as_json
この時点で root_keyがあるのでL103に行って
serializable_hashが呼ばれて、hash_with_metadataが呼ばれる
この時点で"h"という hashが作られる。でこの hを hash_with_metadataに渡す。
serializable_hash
ふかぼらないけど、collectionの時はそれ用のmethodが呼ばれて、returnされてる。
で話をhash_with_metadataに戻す
hash_with_metadata
でそのまま、 @_metaが呼ばれる。
こいつは以下のメソッドでセットされてる。
で最後に _metadata
blockをもらって、処理してからその結果を metaにmergeする。
つまり、今回だと、{}にmergeするので、
{
currentPage: collection.current_page,
totalPages: collection.total_pages,
totalCount: collection.total_count,
nextPage: collection.next_page,
prevPage: collection.prev_page
}
こんな感じ。で、最後に hash_with_metadataの L148で
hash[key] = _metadata(block, meta)だから元のhashにくっつく。
で結果が元のresponseにmeta keyでくっついて返されるという感じらしい。
終わりに
deepwiki便利。codewikiもあったが、codewiki、UIがちょっと使いづらかった印象。
巷でAIAI祭りだが、うまく使っていきたいと思う。