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

  • 26
    Like
  • 5
    Comment

環境

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] }

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