282
233

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

ActiveModel::Serializersとは

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

ActiveModel::Serializersより、

ActiveModel::Serializers brings convention over configuration to your JSON generation.

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

なぜActiveModel::Serializersを使うのか

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

簡単

Railsではjbuilderを使うことが多いですが、記法が直感的じゃなくて多少憶えにくいかな思ってます。

json.(@message, :content, :author)
=> {content: 'hello', author: 'kakkunpakkun'} 

ちょっと変わった文です。

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

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

  def role
    object.role.name
  end
end

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

render json: @message

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

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

速い

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

jbuilderを使った場合

users_controller.rb
def list_with_jbuilder
  @users = User.all
  render :list_with_jbuilder
end

list_with_jbuilder.jbuilder
json.array! @users.each do |user|
  json.partial! 'user', user: user
end
_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を使った場合

users_controller.rb
def list_with_serializer
  users = User.all
  render json: ActiveModel::ArraySerializer.new(
             users,
             each_serializer: UserSerializer
         ).to_json
end
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を見るようにしています。

困った時

ActiveRecord::Baseではないクラスで使いたい

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

こういう感じ

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

なにか値をserializerに渡したい

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

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でjson周りの困ってたことがだいたい解決しました。
スピードが速くてモデルと切り離せるのがとても良いです。

282
233
1

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
282
233

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?