こんにちは 田村 です。
STI を使用して複数の type のモデルデータがあり、これを 1 つの API エンドポイントで混ぜて返したいとします。
このとき、 STI の type に応じて serializer を切り替えたくなります。
今回は、その方法を紹介します。
STI についてはこちらを参照してください。
困ったこと
以下のようなクラス階層を STI で実現しているとします。
SimplePosts は title
と body
を、 QAPosts は question
と answer
のフィールドを持ちます。 共通して posted_at
のフィールドを持ちます。
/api/v1/posts
という API エンドポイントを定義して、下記のような JSON を返したいとします。
{
"posts": [
{
"id": 3,
"type": "QAPost",
"posted_at": "2022-11-02T17:00:00.000Z",
"question": "質問2",
"answer": "質問2の回答です。"
},
{
"id": 4,
"type": "SimplePost",
"posted_at": "2022-11-02T15:00:00.000Z",
"title": "投稿2",
"body": "Consectetur adipiscing elit."
},
{
"id": 2,
"type": "QAPost",
"posted_at": "2022-11-01T16:00:00.000Z",
"question": "質問1",
"answer": "質問1の回答です。"
},
{
"id": 1,
"type": "SimplePost",
"posted_at": "2022-10-31T15:00:00.000Z",
"title": "投稿1",
"body": "Lorem ipsum dolor sit amet."
}
]
}
Posts の type が SimplePost
か QAPost
かによって、 JSON のキーが異なります。
JSON の出力を ActiveModel::Serializers で行っている場合、 Posts の type に応じて、 Serializer を切り替える必要が出てきます。
解決方法
先にコードを示します。
app/serializers/post_serializer.rb
class PostSerializer < ActiveModel::Serializer
attributes :id, :type, :posted_at
def attributes(requested_attrs = nil, reload = false)
@attributes = super(requested_attrs, reload)
case object
when SimplePost
@attributes.merge(SimplePostSerializer.new(object).attributes(nil, reload))
when QAPost
@attributes.merge(QAPostSerializer.new(object).attributes(nil, reload))
end
end
def json_key
'posts'
end
end
app/serializers/simple_post_serializer.rb
class SimplePostSerializer < ActiveModel::Serializer
attributes :title, :body
end
app/serializers/qa_post_serializer.rb
class QAPostSerializer < ActiveModel::Serializer
attributes :question, :answer
end
app/controllers/api/v1/posts_controller.rb
module Api
module V1
class PostsController < ApplicationController
def index
posts = Post.all.order(posted_at: :desc)
render json: posts, each_serializer: PostSerializer
end
end
end
end
PostSerializer で attributes
メソッドをオーバーライドします。各データは object
で取れるため、 case で class を判定し、 SimplePostSerializer.new(object).attributes(nil, reload)
と QAPostSerializer.new(object).attributes(nil, reload)
で、 attributes を生成し @attributes
にマージしています。
json_key
メソッドをオーバーライドしているのは、 json の toplevel object で posts
と表示させるためです。 これを定義しないと、下記のように qa_posts
と、最初に Serializer で処理した type が key としてセットされてしまいます。
{
"qa_posts": [
{
"id": 3,
"type": "QAPost",
"posted_at": "2022-11-02T17:00:00.000Z",
"question": "質問2",
"answer": "質問2の回答です。"
},
...
]
}
なお、 jsonapi_include_toplevel_object
を false
にしている場合は、 json_key
メソッドのオーバーライドは不要です。