こんにちは!スマートバンクでEMをしているmitaniです!
この記事ではRubyのJSON Serializer gemの一つであるAlbaの3.7で追加されたTraits機能について紹介します!!
私たちのチームではRailsをAPIモードで運用しており、SerializerにはActiveModelSerializers(以下、AMS)を利用しています。AMSは機能面で大きな不満はなかったものの、現在はメンテナンスモードとなっており活発な開発は行われていません。
現状、Rails 8への対応など最新バージョンへの追従は行われていますが、今後のメンテナンスへの不安や、より高性能な他のSerializerが登場してきたことなどを考慮し、Albaへ移行することにしました。
移行にあたり、既存のAMSで実装したSerializerのちょっとイケていない部分をAlbaで改善したいと考えました。具体的には、以下のような実装です。
class UserSerializer < ActiveModel::Serializer attribute :id # render_profile? が true であれば name キーを含める attribute :name, if: :render_profile? def render_profile? instance_options[:render_profile] || false end end class UserController < ApplicationController def index render json: User.all, serializer: UserSerializer, render_profile: current_administrator.admin?, status: :ok #=> {"id":1, "name":"スマートバンク太郎"} end end
管理画面などでログインしている管理者の権限に応じて表示項目を切り替える際、これまではAMSのinstance_options
を利用してフラグを渡し、表示内容を制御していました。
しかしこの方法では、instance_options
に渡された引数がSerializer内部でどのように利用されるのかが追いづらく、また、キー名や値に対するバリデーションも効かないため、可読性や保守性の面で課題を感じていました。
今回、AMSから載せ替えするgemを比較検討している際、Blueprinterの Views という機能があることを知りました。
class UserBlueprint < Blueprinter::Base identifier :uuid field :email, name: :login view :normal do fields :first_name, :last_name end view :extended do include_view :normal field :address association :projects end end puts UserBlueprint.render(user, view: :extended)
こういう機能がAlbaにも欲しい!と思い、Albaの作者である大倉さん(okuramasafumi)に相談したところ、快くご対応いただけることになりました!
@shohei1913 https://t.co/KLxQKBE9Rj RubyKaigiの懇親会で話していたAlbaの機能、こんな感じで実装してみたんですが、想定されているものと合っていますでしょうか。
— 大倉雅史(OKURA Masafumi) (@okuramasafumi) 2025年5月7日
AlbaのTraits機能とは
Traitsとは、Serializerに定義されたattributesをグループ化し、呼び出し側でそのグループを利用するかどうかを指定できる機能です。この機能はAlba 3.7から利用可能です。
class User attr_accessor :id, :name, :email def initialize(id, name, email) @id = id @name = name @email = email end end class UserResource include Alba::Resource attributes :id trait :additional do attributes :name, :email end end user = User.new(1, 'Foo', 'foo@example.com') UserResource.new(user).serialize # => '{"id":1}' UserResource.new(user, with_traits: :additional).serialize # => '{"id":1,"name":"Foo","email":"foo@example.com"}'
詳細は公式ドキュメントも参照してください。
ユースケース
私たちの実際のコードベースでは、主に以下のようなケースでTraits機能を活用できます。
権限管理
冒頭で触れた、instance_options
を使っていた権限ベースの表示切り替えは、Traitsを使うことで以下のようにシンプルに実現できます。
class UserSerializer include Alba::Serializer attributes :id trait :with_profile do attributes :name end end class UserController < ApplicationController def index render json: UserSerializer.new(User.all, with_traits: user_traits), status: :ok end private # 権限を持っている人のみプロフィールを返却するTraitを指定する def user_traits current_administrator.admin? ? :with_profile : nil end end
render_profile?
のような条件分岐メソッドが不要になり、コードが非常にスッキリしましたね!👍 Controllerから見たときに、どのような出力がされるのか予期しやすいのが良いです。さらに、不正なtrait名を指定すると実行時エラーが発生するため、typoなどの単純なミスにも気付きやすくなるというメリットもあります。
Serializerの部分的な共通化と切り替え
他にも、例えば関連する複数のリソースで共通のattribute群を定義しつつ、リソース特有のattributeを追加したい場合など、Serializerの部分的な共通化と動的な切り替えにTraitsを活用できます。
class DepositSerializer include Alba::Serializer attributes :id trait :with_convenience_store do one :convenience_store, serializer: ConvenienceStoreSerializer end trait :with_credit_card do one :credit_card, serializer: CreditCardSerializer end end class ConvenienceStoreController < ApplicationController def index # コンビニの入金情報を返したいのでwith_convenience_storeを指定 render json: DepositSerializer.new(ConvenienceStore.all, with_traits: :with_convenience_store), status: :ok end end class CreditCardController < ApplicationController def index # クレカの入金情報を返したいのでwith_credit_cardを指定 render json: DepositSerializer.new(CreditCard.all, with_traits: :with_credit_card), status: :ok end end
APIエンドポイント(index / show)ごとのデータ切り替え
indexアクションとshowアクションのように、同じリソースでもAPIエンドポイントごとに返却するデータ構造を変えたい場合にもTraitsは有効です。
class UserSerializer include Alba::Serializer attributes :id, :name trait :show do one :profile, serializer: ProfileSerializer many :access_logs, serializer: AccessLogSerializer end end class UserController < ApplicationController def index render json: UserSerializer.new(User.all), status: :ok end def show # showのエンドポイントだけTraitを指定 render json: UserSerializer.new(User.first, with_traits: :show), status: :ok end end
まとめ
ご覧いただいたように、AlbaのTraits機能は非常に便利で、Serializerの実装をより柔軟かつ直感的にしてくれますね!
私たちのチームでは現在、AMSからAlbaへの移行を鋭意進行中です。移行の過程で見られた工夫や直面した課題、そして気になる移行後のパフォーマンス改善結果などについても、続編としてブログでお届けできればと考えていますので、ぜひご期待ください!