こんにちは。虎の穴ラボのH.Kです。
ちょっと前まではJavaを書いていましたが、最近はRuby on Railsでアプリケーションの開発を行っています。
Railsアプリケーションの性能改善の一環として、JSONシリアライザを「ActiveModelSerializers」から「jb」に切り替えました。
今回は、その手法をお伝えします。
それぞれのシリアライザについて
ActiveModelSerializers
github.com
言わずと知れた(?)Railsのシリアライザで Jbuilderとともによく使われています。
複雑なJSONであるほど真価を発揮するような構造になっています。
これはベースとしてテンプレートを返却する思想のJbuilderに対して、クラスとして定義し、定義に従いシリアライズするという思想があるためです。
jb
先述のJbuilderを拡張して作成されています。
特徴として、上記のGitのReadmeに以下のことが挙げられています。
・No ugly builder syntax
・No method_missing calls
・render_partial with :collection option actually renders the collection (unlike Jbuilder)
つまりJbuilderの使いにくかった部分を改良したGemとなっています。
今回の変更理由ですが、jbが非常に高速であることが挙げられます。
余談ですが、私の中でJbuilder
といえばJavaのIDEのほうが先に浮かびます。
導入
Gitのほうにも書いてあるとおり、他のGemと同じようにGemfileに書いてbundle install
で導入できます。
gem 'jb'
書き換えのポイント
実際にどのように書き換えたのか例示を交えて解説します。
継承関係
ActiveModelSerializersでは継承関係を使って、プロパティを引き継いでいくことができます。
例えば以下のような形です。
user_serializer.rb
class UserSerializer < ActiveModel::Serializer attributes :id, :name end
user_detail_serializer.rb
class UserDetailSerializer < UserSerializer attributes :age end
UserDetailSerializer
を使いシリアライズすると以下のようなJSONが生成できます。
{ "id": 1, "name": "h.k", "age": 28 }
これをjbで実現するためには2つの方法が考えられます。
- 継承関係を意識して移行する
- 一つのテンプレートにまとめてしまう
今回は1の方法で実施します。なお、実行速度だけを考えれば2を取るべきかと思います。
user_serializer.json.jb
res = { id: user.id, name: user.name } res
user_detail_serializer.json.jb
res = render partial: 'api/user/user_serializer', locals: {user:user} # 先にuser_serializer側のオブジェクトを受け取る res[:age] = user.age # user_serializerで作成したオブジェクトにプロパティを追加する res
こうすることによってUserDetailSerializer
と同じ形式のJSONを返却することができます。
なお、以下のようにhash#merge
を使用しても実装できます。見通しはこの方が良いですが、mergeメソッドがあまり高速ではないためおすすめしません。
res = render partial: 'api/user/user_serializer', locals: {user:user} # 先にuser_serializer側のオブジェクトを受け取る tmp = { age: user.age } res.merge(tmp)
メソッド化されたプロパティ
先程のUserDetailSerializerを修正して例示します。
class UserDetailSerializer < UserSerializer attributes :age, :display_adult_items def display_adult_items object.age >= 18 # シリアライズする対象のモデルがobjectに入っている end end
このように導出した結果をJSONで返却したい場合、ActiveModelSerializers
ではメソッド化することで実装できます。
これをjbで実現するためにはワンライナーで実装するか変数として処理する必要があります。
※あくまでテンプレートであり、メソッドを作成することができないためです。
変数として処理をする場合もコントローラ側からlocalsを使って引き渡す方法とテンプレート内で定義する方法があります。
コントローラ側から渡す場合、以下のように実装できます。
UserController.rb
display_adult_items = user.age >= 18 # 先に導出する render 'api/user/user_detail_serializer', formats: 'json', handlers: 'jb', locals: { user: user, display_adult_items: display_adult_items} # localsで引き渡す
user_detail_serializer.json.jb
res = render partial: 'api/user/user_serializer', locals: {user:user} res[:age] = user.age res[:display_adult_items] = display_adult_items # 引き渡されたdisplay_adult_itemsをそのまま設定する res
この方法の注意点としては、user_detail_serializer.json.jb
を他のテンプレートから呼び出そうとした際にdisplay_adult_items
をlocalsでちゃんと設定しておかないとエラーになってしまう点です。
次にテンプレート内で定義する方法です。といってもそのまま愚直にやるだけです。
user_detail_serializer.json.jb
res = render partial: 'api/user/user_serializer', locals: {user:user} res[:age] = user.age display_adult_items = user.age >= 18 # 先に導出する res[:display_adult_items] = display_adult_items # 設定する res
リレーション
今回はUserが複数のPost(投稿)を保持するケースで例示します。
まずはActiveModelSerializers
です。PostSerializer
は作成済みとして読んでください。
user_detail_serializer.rb
class UserDetailSerializer < UserSerializer attributes :age, :posts has_many :posts, serializer: PostSerializer end
これだけで複数Postのシリアライズまでできます。
次にこれをjbを使ったシリアライズに書き換えます。
user_detail_serializer.json.jb
res = render partial: 'api/user/user_serializer', locals: {user:user} res[:age] = user.age res[:posts] = render partial: 'api/posts/post_serializer', collection: user.posts, as: :post res
collectionで繰り返す対象を指定して、as: :post
で変数名を設定します。
これにより一つのpostをシリアライズするapi/posts/post_serializer
で複数の投稿を扱えます。
つまったポイント
ここからはjbに書き換える上で少し詰まったところと解決方法を示します。
空の文字列が返ってしまう
ちゃんとissueに解決方法が書いてありました。
結論としてはApplicationController
にActionView::Rendering
が必要でした。
もともとactive_model_serializers
で使っていたControllerをそのまま書き換えた結果、ActionView::Rendering
を設定しておらず気づけませんでした。
class Api::UsersController < Api::ApplicationController include ActionView::Rendering # 追加しないとテンプレートを返せない end
Helperは使える?
こちらはJBuilderのissueに書いてありました。
ApplicationController
を継承したクラスにhelper ApplicationHelper
を追加するだけで、他のviewsと同じようにHelperが使用できます。
class Api::UsersController < Api::ApplicationController helper ApplicationHelper # 追加するとヘルパーが使える include ActionView::Rendering end
別件:Windows×Rails×RuboCop
このブログ書くために私用のWindows機にRails環境を作っていたのですが、RuboCop入れたら以下の警告がたくさん出て大変でした。
Layout/EndOfLine: Carriage return character missing.
何が起きていたかというと、rails new プロジェクト名
で作成された各ファイルの改行コードがLFで設定されていました。
RubocopのLayout/EndOfLine
の警告を確認するとWindowsではReturn character is CR+LF on Windows.
とあります。
これを解消するためにすべてのファイルの改行コードをCR+LF
に変換するのが地味に大変でした。
最近ではWSL2も便利みたいなので、開発環境をそちらに移すことも検討したいところです……。
まとめ
今回のシリアライザの書き換えで最大で25%ほどの速度改善ができました。
まだいくつかのAPIしか切り替えていませんが、ほとんど変わらなかったところもあります。
今後もなにか良い改善ができれば、ブログにて発信をしていきたいと思います。
P.S
【Amazonギフト券3,000円分がもらえる!?】虎の穴ラボ公式Twitterリツイートキャンペーン!
現在、虎の穴ラボ公式Twitterにて「エンジニアの働き方調査!」と題してリツイートキャンペーンを開催しています。
■キャンペーン概要
エンジニアにとってより良い環境を整える事を目的に、皆様の考える今後の働き方と現状についてお考えを知りたいと思い、虎の穴ラボ公式Twitter(@toranoana_lab)にてアンケート調査を行っています!
虎の穴ラボ公式Twitterをフォロー&RTの上、アンケートにご回答くださった方を対象に、Amazonギフト券3,000円分を抽選で20名様にプレゼント!
アンケートの回答期限は8/20(木)23:59までとなりますので、皆様のお考えを聞かせて頂けると嬉しいです。
🔻「エンジニアの働き方調査!」ツイートはこちら
【エンジニアの働き方調査】
— 虎の穴ラボ (@toranoana_lab) 2020年8月14日
アンケートに回答して頂いた方の中から、抽選で20名様にAmazonギフト券3,000円分をプレゼント!
▼応募方法
①@toranoana_lab フォロー
②本ツイートをRT
③下記アンケートに回答https://t.co/6Y8CQ1xTpY
回答期限:8/20(木)23:59まで
皆様のご参加をお待ちしています!
虎の穴ラボ主催LT会開催!
虎の穴ラボ主催のオンラインLTイベントを 8/26(水)19:30〜 開催します!
今回もフリーテーマとなっており、ITに関連する内容であれば、何でも大歓迎ですので、初心者の方も練習の場としてお気軽にご参加ください!
connpassにて参加受付中です!
その他採用情報
虎の穴ラボでの開発に少しでも興味を持っていただけた方は、採用説明会やカジュアル面談という場でもっと深くお話しすることもできます。ぜひお気軽に申し込みいただければ幸いです。
カジュアル面談では虎の穴ラボのエンジニアが、開発プロセスの内容であったり、「今期何見ました?」といったオタクトークから業務の話まで何でもお応えします。
カジュアル面談や採用情報はこちらをご確認ください。
yumenosora.co.jp