Dynamoid で where を実行したら Chain クラスが返るので ActiveModelSerializer は効かないらしい
Ruby on Rails で DynamoDB を ActiveRecord のように扱える Dynamoid を使用しています。
Controller から、とあるモデルの配列を返したかったのですが、うまく ActiveModelSerializer が働かずいろいろ調べたので備忘録としてまとめます。
2024/02/19 14:10 pluck メソッドに関する記述を追加
結論
- where では Dynamoid::Criteria::Chain クラスが返るので、to_a などで変換する必要がある
- project メソッドで列指定はできるけど、STI していると type 列も返ってくるので serializer との併用がおすすめ
- pluck メソッドなら、列指定したうえで type 列を含まない配列を返せます
問題になったコード
モデル名などは架空です。
特定ユーザーの属性一覧を取得するクエリで、JSON の配列を取得する想定です。
./app/controllers/api/v2/user_controller.rb
class Api::V2::UserController < Api::V2::BaseController
def index
user_id = params[:id]
r = User.where(
pk: "U_##{user_id}",
"sk.begins_with": "attributes_#"
)
render json: r,
root: 'data',
adapter: :json,
each_serializer: UserSerializer,
status: :ok,
status_code: 200
end
end
./app/serializers/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
attributes :attribute_id, :attribute
end
このコントローラーを起動すると、serializer が効かずに、以下のようなレスポンスが返ってきます。
[
{
"data: {
"id": "xxxx",
"attribute_id": "xxxx",
"name": "xxxx",
"attribute_name": "xxxx"
}
},
{
"data: {
"id": "xxxx",
"attribute_id": "xxxx",
"name": "xxxx",
"attribute_name": "xxxx"
}
} # 略....
]
理想形はこんな感じです。
{
"data": [
{
"id": "xxxx",
"attribute_id": "xxxx"
},
{
"id": "xxxx",
"attribute_id": "xxxx"
} # 略....
]
}
原因
User.where の返り値が Dynamoid::Criteria::Chain であり、Array ではないため、コレクションに対して作用する each_serializer が効かなかったと思われる(Chain クラスの superclass が Object だった)。
対策
array に変換してから render する。
./app/controllers/api/v2/user_controller.rb
class Api::V2::UserController < Api::V2::BaseController
def index
user_id = params[:id]
r = User.where(
pk: "U_##{user_id}",
"sk.begins_with": "attributes_#"
)
render json: r.to_a,
root: 'data',
adapter: :json,
each_serializer: UserSerializer,
status: :ok,
status_code: 200
end
end
ちなみに、where のあとに、project
メソッドでチェーンすれば指定列の抽出ができますが、STI(単一テーブル継承) していると type 列も返ってきてしまうので serializer も併用したいところです。
r = User.where(
pk: "U_##{user_id}",
"sk.begins_with": "attributes_#"
).project(:id)
# User がなんかのテーブルを継承していると、type 列も返ってくる
# 例) [#<User pk: nil, sk:nil, id: xxx, type: "User",....>, #<User ....>, ...]
また、pluck
メソッドでチェーンすれば指定列の抽出ができるかつ、Array に変換できるので用途が決まっているのならこの方法がいいかもしれないです。
r = User.where(
pk: "U_##{user_id}",
"sk.begins_with": "attributes_#"
).pluck(:id)
# 例) [1, 2, 3 ...]
参考
Discussion