Edited at

ActiveModel::Serializersでネストされたリソースを扱う

More than 1 year has passed since last update.


環境

Rails 5.0.0.1

active_model_serializers 0.10.2


概要

リソースを2段階以上ネストしたときに、2段階目以降がシリアライズの結果に入ってこない。

include オプションを使う。


詳細


テーブル

こんな感じ。

テーブル名
カラム

hoges
id, hogehoge

fugas
id, hoge_id, fugafuga

bars
id, fuga_id, barbar


モデル

普通にhas_manyになっている。

class Hoge < ApplicationRecord

has_many :fugas
end

class Fuga < ApplicationRecord
has_many :bars
end

class Bar < ApplicationRecord
end


Serializer

次のように書く。

# app/serializers/hoge_seralizer.rb

class HogeSerializer < ActiveModel::Serializer
attributes :id, :hogehoge
has_many :fugas

class FugaSerializer < ActiveModel::Serializer
attributes :id, :fugafuga
has_many :bars

class BarSerializer < ActiveModel::Serializer
attributes :id, :barbar
end
end
end

注: ネストして書いたが、別のファイルに書いてもよい。両方書くと自分の中のクラスが優先される。

詳細は ここ の Model Associations and Nested Serializers っていうところにある。


Controller

Controllerで次のように書く。

@hoge = Hoge.first

render json: @hoge


結果

結果のjsonが以下のようになっている。

{

id: 1,
hogehoge: 'hoge',
fugas: [
{
id: 1,
fugafuga: 'fuga'
// ここに bars が入ってこない!
}
]
}


対応方法

active_model_serializers 0.10より、 ネストされたリソースは自動的に展開されなくなったとのこと。

https://github.com/rails-api/active_model_serializers/issues/1077

renderに include というオプションを渡すと、入ってくるようになる。

@hoge = Hoge.first

render json: @hoge, include: { fugas: [:bars] }

結果

{

id: 1,
hogehoge: 'hoge',
fugas: [
{
id: 1,
fugafuga: 'fuga',
bars: [ // 入ってきた
{
id: 1,
barbar: 'bar'
}
]
}
]
}

ActiveRecordに対するincludes/preloadと同じような形式で渡すことができる。

※参考:公式ドキュメント https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/general/adapters.md#include-option


落穂拾い


デバッグ

render でやり続けてるとデバッグが効率わるい。もう少しローレベルなAPIを使ってデバッグする。

ActiveModelSerializers::SerializableResource.new(Hoge.first, include: { fugas: :bars }).as_json

詳細このへん


キー名を変えているときは、変えたあとのキー名で指定する

シリアライザでキー名を変えることができる。こんなふうに

class HogeSerializer < ActiveModel::Serializer

attributes :id, :hogehoge
has_many :fugas, key: :foos
end

この場合は、変えたあとのキー名 foos を指定する。

render json: @hoge, include: { foos: [:bars] }

よくよく考えれば当然なんだけど、これにちょっとハマった。