LoginSignup
26
11

More than 3 years have passed since last update.

active model serializers のレシピ集 & 個人的ベストプラクティス集

Last updated at Posted at 2021-04-24

はじめに

  • 他に便利な使い方があれば教えてもらえると幸いです
  • 間違っている箇所があれば教えてもらえると幸いです

レシピ

条件付き attribute

条件でキーの有無が変わるときは下記のように書けます。

app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  attribute :posts_count, if: -> { object.posts.present? } do
    object.posts.count
  end
end

シリアライザ内で使えるパラメーターを渡す

シリアライザにキー、バリューを渡すことで

app/controllers/users_controller.rb
class UsersController < Api::ApplicationController
  def show
    render json: current_user,
           serializer: UserSerializer,
           hoge: 'test'
  end
end

or

ActiveModelSerializers::SerializableResource.new(
  user,
  serializer: UserSerializer,
  adapter: :attributes,
  hoge: 'test'
).serializable_hash

シリアライザ内において instance_options からその値を参照することができます。

app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  attribute :test_val do
    instance_options[:hoge]
  end
end

個人的ベストプラクティス

シリアライザは明示的に指定した方が良さそう

シリアライザを明示的に指定なくてもモデルのクラス名から推測して UserSerializer を使ってくれますが、

app/controllers/users_controller.rb
class UsersController < Api::ApplicationController
  def show
    render json: current_user
  end
end
  • UserSerializer を変更したときの影響範囲を調べるときにシリアライザ名で grep しにくい
  • 一目でどのシリアライザを使っているかわかりにくい

ことからシリアライザを明示的に指定した方が良さそうです。

app/controllers/users_controller.rb
class UsersController < Api::ApplicationController
  def show
    render json: current_user,
           serializer: UserSerializer
  end
end

attribute とメソッドを2つ使うよりは attribute のみでキーを出力した方が良さそう

attribute とメソッド定義を2つ書くより

app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  attribute :name_hoge

  def name_hoge
    "#{object.name}_hoge"
  end
end

attribute ブロックのみを使った方がシンプルになります。

app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  attribute :name_hoge do
    "#{object.name}_hoge"
  end
end

proc のため return は使えないことに注意してください

メソッドの処理そのままでメソッド名と異なるキー名を使いたいときは attribute を使った方が良さそう

attribute ブロックを書くより

attribute :is_admin do
  object.admin?
end

attribute メソッドの key オプションを使った方がシンプルに書けます。

app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  attribute :admin?, key: :is_admin
end

リソースのハッシュ出力では adapter: :attributes オプションを使ってルートキーを出力させない方が良さそう

serializable_hash[:post] のようにキー名を指定する書き方をするとシリアライザの名前空間が変わったり、type メソッドに渡す値が変わるとキー名が変わってしまいデグレが起こる可能性があるので、ルートキーが必要ない場合は adapter: :attributes を使用してキー名を記載しない方が良いと思います。

単一リソース

ActiveModelSerializers::SerializableResource#serializable_hash を使います。
serializer オプションにシリアライザを指定します。

app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  attribute :single_post do
    ActiveModelSerializers::SerializableResource.new(
      object.single_post,
      serializer: PostSerializer,
      adapter: :attributes
    ).serializable_hash
  end
end

複数リソース

each_serializer オプションにシリアライザを指定します。

app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  attribute :posts do
    ActiveModelSerializers::SerializableResource.new(
      object.posts,
      each_serializer: PostSerializer,
      adapter: :attributes
    ).serializable_hash
  end
end

アソシエーションをシリアライズするときは has_many, has_one, belongs_to を使うよりも手動でシリアライズした方がパラメーターを渡すことができるので良さそう

アソシエーションも返却するときは has_many などのメソッドを使えばシンプルに書くことができますが、

app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  has_many :posts, serializer: PostSerializer
end

パラメーターを渡すことはできません

app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  # hoge_param は PostSerializer に渡らない
  has_many :posts, serializer: PostSerializer, hoge_param: 'hoge'
end

アソシエーションのシリアライザにパラメーターを渡したいときは手動でシリアライザを展開しなければなりません

app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  attribute :posts do
    ActiveModelSerializers::SerializableResource.new(
      object.posts,
      each_serializer: PostSerializer,
      hoge_param:'hoge',
      adapter: :attributes
    ).serializable_hash
  end
end

ルートキーにメタ情報を追加したいときは meta オプションを使う方が良さそう

キー名にこだわりがなくルートキーと同じ階層にメタ情報を追加したいときは meta を使うと楽です。

app/controllers/users_controller.rb
class UsersController < Api::ApplicationController
  def show
    render json: current_user,
           serializer: UserSerializer
           meta: {time: Time.zone.now}
  end
end

meta を使わない場合は hash を自前で作ることになるのでコードが煩雑になると思われます。

app/controllers/users_controller.rb
class UsersController < Api::ApplicationController
  def show
    hash = { user: ActiveModelSerializers::SerializableResource.new(current_user, 
             serializer: UserSerializer, adapter: :attributes).serializable_hash, 
              meta: {time: Time.zone.now} 
           }
    render json: hash
  end
end

ディレクトリの切り方はコントローラーに準じた方が良さそう

使いたい user serializer がコントローラーのメソッドごとに違う場合は

app/controllers/users_controller.rb
class UsersController < Api::ApplicationController
  def index
    ...
  end

  def show
    ...
  end
end

serializers 配下にフラットにシリアライザファイルを作成するよりも

app/serializers/index_user_serializer.rb
app/serializers/show_user_serializer.rb

返却されるオブジェクトはAPIのエンドポイントに依存しているので、コントローラーのクラス名の名前空間、メソッドに応じてディレクトリを切った方がわかりやすいと思っています。

app/serializers/users/index/user_serializer.rb
app/serializers/users/show/user_serializer.rb

汎用的ではなく1つのエンドポイントでしか使われないシリアライザはシリアライザ内にクラス定義した方が良さそう

1つのエンドポイントでしか使われない特殊なシリアライザならばファイルに切り出して、コードを参照するときに別ファイルを見にいくよりも

app/serializers/special_post_serializer.rb

シリアライザクラス内にシリアライザを定義した方がコードを追いやすいかもしれません。

app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  attribute :special_post do
    ActiveModelSerializers::SerializableResource.new(
      object.posts,
      serializer: UserSerializer::SpecialPostSerializer,
      adapter: :attributes
    ).serializable_hash
  end

  class SpecialPostSerializer < ApplicationSerializer
    attribute :special_post_key do
      'value'
    end
  end
end

名前空間が変わらないので基本的には type を指定した方が良さそう

type メソッドを使うことでルートキーの名前を固定することができます。

app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
 type 'user'
  attribute :name
end
ActiveModelSerializers::SerializableResource.new(
  user,
  serializer: UserSerializer,
).serializable_hash
=> {user: {...}}

type を指定しないとシリアライザの名前空間によってルートキーの名前が変わってしまいます。

app/serializers/v2/user_serializer.rb
class V2::UserSerializer < ApplicationSerializer
  attribute :name
end
ActiveModelSerializers::SerializableResource.new(
  user,
  serializer: V2::UserSerializer,
).serializable_hash
=> {'v2/user' => {...}}

名前空間に関わらず一意のリソースの名前を使うことの方が多いと思うので type は指定した方が良いと思われます。

# ルートキーが変わるとこのようなコードがデグレを起こす可能性がある
user_res = ActiveModelSerializers::SerializableResource.new(...).serializable_hash
user_res[:user][:name]

シリアライザ内でデータの出どころがわかりにくいので scope は使わない方が良さそう

scope を使用すると指定した情報をそのままシリアライザに持っていくことができますが、下記の例の場合 current_post がどこから来たのかがわかりにくいため

app/controllers/users_controller.rb
class UsersController < Api::ApplicationController
  def show
    render json: current_user,
           serializer: UserSerializer,
           scope: current_user.current_post,
           scope_name: :current_post
  end
end
app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  attribute :post_name do
    current_post.name
  end
end

通常のパラメーターで渡した方がわかりやすいと思っています。

app/controllers/users_controller.rb
class UsersController < Api::ApplicationController
  def show
    render json: current_user,
           serializer: UserSerializer,
           current_post: current_user.current_post
  end
end
app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  attribute :post_name do
    instance_options[:current_post].name
  end
end

has_many のアソシエーションで order を使うときはそれ専用のアソシエーションをモデルで定義した方が良さそう

下記記事を参照してください。

has_manyで紐づくモデルをSerializerの中でorderするとSQLキャッシュのN+1が起きる問題をなんとかしたい

26
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
11