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

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

# レシピ

## 条件付き attribute

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

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

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

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

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

or

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

```

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

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

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

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

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

```ruby:app/controllers/users_controller.rb
class UsersController < Api::ApplicationController
  def show
    render json: current_user
  end
end
```

- UserSerializer を変更したときの影響範囲を調べるときにシリアライザ名で grep しにくい
- 一目でどのシリアライザを使っているかわかりにくい

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

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

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

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

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

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

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

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

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

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

attribute ブロックを書くより

```ruby
attribute :is_admin do
  object.admin?
end
```

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

```ruby: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 オプションにシリアライザを指定します。

```ruby: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 オプションにシリアライザを指定します。

```ruby: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 などのメソッドを使えばシンプルに書くことができますが、

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

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

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

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

```ruby: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 を使うと楽です。

```ruby: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 を自前で作ることになるのでコードが煩雑になると思われます。

```ruby: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 がコントローラーのメソッドごとに違う場合は

```ruby: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
```

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

```ruby: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 メソッドを使うことでルートキーの名前を固定することができます。

```ruby:app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
　type 'user'
  attribute :name
end
```

```ruby
ActiveModelSerializers::SerializableResource.new(
  user,
  serializer: UserSerializer,
).serializable_hash
=> {user: {...}}
```

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

```ruby:app/serializers/v2/user_serializer.rb
class V2::UserSerializer < ApplicationSerializer
  attribute :name
end
```

```ruby
ActiveModelSerializers::SerializableResource.new(
  user,
  serializer: V2::UserSerializer,
).serializable_hash
=> {'v2/user' => {...}}
```

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

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


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

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

```ruby: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
```

```ruby:app/serializers/user_serializer.rb
class UserSerializer < ApplicationSerializer
  attribute :post_name do
    current_post.name
  end
end
```

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


```ruby: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
```

```ruby: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が起きる問題をなんとかしたい](https://qiita.com/rh_taro/items/b09b3ebb68c2566c1ee5#2-has_many%E3%81%A7%E7%B4%90%E3%81%A5%E3%81%8F%E3%83%A2%E3%83%87%E3%83%AB%E3%82%92serializer%E3%81%AE%E4%B8%AD%E3%81%A7order%E3%81%99%E3%82%8B%E3%81%A8sql%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E3%81%AEn1%E3%81%8C%E8%B5%B7%E3%81%8D%E3%82%8B%E5%95%8F%E9%A1%8C%E3%82%92%E3%81%AA%E3%82%93%E3%81%A8%E3%81%8B%E3%81%97%E3%81%9F%E3%81%84)