ActiveModel::Serializersを使ってサクサクjsonを出力しよう

# ActiveModel::Serializersとは

Railsなどで簡単で素早くjsonを作れるgemです。

[ActiveModel::Serializers](https://github.com/rails-api/active_model_serializers)より、
> ActiveModel::Serializers brings convention over configuration to your JSON generation.


実際に試してはないですが、ActiveModelに依存しているだけなのでSinatraなどでも使えるはずです。


# なぜActiveModel::Serializersを使うのか

簡単で速いので使ってます。

## 簡単

Railsでは[jbuilder](https://github.com/rails/jbuilder)を使うことが多いですが、記法が直感的じゃなくて多少憶えにくいかな思ってます。

```
json.(@message, :content, :author)
=> {content: 'hello', author: 'kakkunpakkun'} 
```
ちょっと変わった文です。



ActiveModel::Serializerならこんな感じです。

```rb
class MessageSerializer < ActiveModel::Serializer
  attributes :content, :author, :role

  def role
    object.role.name
  end
end
```

呼び出すのもrenderを使えます

```rb
render json: @message
```

ActiveModel::Serializerを継承して使いたいattributesを指定するのが基本です。
roleのようにattributeをオーバーライドするようなことも出来ます。
普通のRubyのクラスなのでテストも書きやすいです。


実際、HTMLを生成するのにはテンプレートがあるといいと思うんですが、jsonはもっとシンプルだし、テンプレートは必要ないんじゃないかと思っていました。
とはいえ、to_jsonだとモデルをいじらなくてはいけなくなってプレゼンテーション層と密着した感じになってしまいます。
ActiveModel::Serializersはそういうjsonの出力をさくっと簡単に担ってくれます。

## 速い

partialを使うとjbuilderはものすごく遅くなります。
適当にusersをリストアップする場合で計ってみました。

### jbuilderを使った場合
```rb:users_controller.rb
def list_with_jbuilder
  @users = User.all
  render :list_with_jbuilder
end

```

```rb:list_with_jbuilder.jbuilder
json.array! @users.each do |user|
  json.partial! 'user', user: user
end
```

``` rb:_user.jbuilder
json.id user.id
json.email user.email
json.name user.display_name
```

だいたいusersのようなコレクションをリストにするときはuserの情報を他のところからも呼び出したいことがあるのでpartial化するのですが、そうするとここではuserを100件出力したところ **280〜350ms** ほどかかりました。

userを5件だけに絞ると50〜60msで済むのですが、partialを呼び出す件数が多くなればなるほど遅くなっていきます。

これは恐らくpartialを呼ぶたびに0.1〜0.3msほどかかっているためではないかと思います。

```
I, [2014-08-31T15:30:38.740573 #65161]  INFO -- : Processing by UsersController#list_with_jbuilder as */*
D, [2014-08-31T15:30:38.748207 #65161] DEBUG -- :   User Load (1.8ms)  SELECT `users`.* FROM `users`
I, [2014-08-31T15:30:38.755020 #65161]  INFO -- :   Rendered users/_user.jbuilder (0.2ms)
I, [2014-08-31T15:30:38.757443 #65161]  INFO -- :   Rendered users/_user.jbuilder (0.2ms)
I, [2014-08-31T15:30:38.759521 #65161]  INFO -- :   Rendered users/_user.jbuilder (0.2ms)

# ↑これがuserの件数分続く
```

partial使わなければいいんですが、そうすると変更箇所が増えて維持が大変になります。

### ActiveModel::Serializersを使った場合

```rb:users_controller.rb
def list_with_serializer
  users = User.all
  render json: ActiveModel::ArraySerializer.new(
             users,
             each_serializer: UserSerializer
         ).to_json
end
```

```rb:user_serialiser.rb
class UserSerializer < ActiveModel::Serializer
  attributes :id, :email, :name

  def name
    object.display_name
  end
end
```

jsonの配列を作る書き方は色々ありますが、jbuilderの場合と同様にuserの情報を他のところからも呼び出せるように`UserSerializer`を作っておきました。
同じデータを検索したところ、serializerを使った場合は100件で **40ms〜50ms** くらいでした。1/7くらいでしょうか

200件300件と増えれば増えるほど差が開きます。


## テストが書きやすい

シリアライズするだけのクラスが出来るのでそのクラスをテストするのもやりやすいです。
ARのModelの中にas_jsonやto_builderを書いていくと、いくつか出力したいjsonのパターンを準備したくなると途端に条件分岐や他のメソッドの呼び出しが増えて煩雑になることがあります。
テストも書きにくくなります。

ActiveModel::Serializerを継承してますが比較的シンプルなクラスなのでテストも普通のクラスについて書くように書けるので楽です。



# 難点
自分はあまり困ってないですが、場合によっては難点かもなと思うところもあります。

## ActiveModelに依存する

名前の通り、ActiveModelに依存しています。
ActiveModel::ModelとActiveModel::Serializationを使います。

僕はActiveModelはActiveModel::Validationsとか便利だし、Rails使おうとPadrino使おうと永続化にはActiveRecord使いたいのでActiveModelに依存しちゃっても全然構わないのですが、ActiveModelを利用したい訳じゃない時には嫌なことはあるかも。

## 次のバージョンで変更が大きそう

現在0.8.xが安定版なんですが、今度出る0.10.xは色々変わりそうです。
まあこれはどのgemでもあるかなと思いますし、じっくりウォッチするしかないですね。

ドキュメントも変更中のせいか量がすごく減っているので[0.8のREADME](https://github.com/rails-api/active_model_serializers/blob/0-8-stable/README.md)を見るようにしています。

# 困った時
## ActiveRecord::Baseではないクラスで使いたい

ActiveModel::ModelとActiveModel::Serializationをincludeしたらserializerに渡して使えます。

こういう感じ

```rb
class Post
  include ActiveModel::Serialization
  include ActiveModel::Model 
end
```

## なにか値をserializerに渡したい

こまったらoptionsを渡せば何とかなります。

```rb
ActiveModel::ArraySerializer.new(
  users,
  each_serializer: UserSerializer,
  message: message # serializer内でoptions[:message]で呼び出せる
)
```

ただ、これは今度出る0.10では無くなるかもしれません。
代わりのものが候補に挙がってるようですがこれというのが決まってないようです。


## 関連するモデルを出力したい

has_manyやhas_oneも使えます(あくまで多重度が1か多かを示すだけなのでbelongs_toはありません)

`has_many: :posts`みたいに書けます。
どのserializerを使うかも指定できるので関連したモデルの必要なattributesだけ出すのも簡単です。
`has_many: :posts, serializer: ShortPostSerializer`


## ジェネレータを使いたい

ジェネレーターもあります。

```
rails g serializer post
```


# というわけで

[ActiveModel::Serializer](https://github.com/rails-api/active_model_serializers)でjson周りの困ってたことがだいたい解決しました。
スピードが速くてモデルと切り離せるのがとても良いです。