開発ノート@HarikoApps
HarikoApps: https://hariko.sprkls.me
2024-06-21ActiveModel::Serializers::JSON を使ったシンプルなシリアライザ #11
RailsでJSON APIを実装しているプロジェクトでシリアライザがなかったものにシリアライザを導入した際に、Railsに含まれるActiveModel::Serializers::JSONを用いたクラスを使ようにしてみました(確認環境:Rails 7.1.3.4)。
下記のようにベースクラスを定義し、適宜継承して使うような実装にしました。ドキュメントにattributesメソッドを定義するように書かれているのでそれに従い、簡潔に書けるようにexposed_attribute_namesというメソッドを子クラスでオーバーライドするようにしています。
下記のようにベースクラスを定義し、適宜継承して使うような実装にしました。ドキュメントにattributesメソッドを定義するように書かれているのでそれに従い、簡潔に書けるようにexposed_attribute_namesというメソッドを子クラスでオーバーライドするようにしています。
class BaseEntity include ActiveModel::Serializers::JSON def exposed_attribute_names [] end def attributes # Attribute名の配列を、Attribute名をキーにしたnil値をもつハッシュに変換 # 例) [:id, :name] => { "id" => nil, "name" => nil } exposed_attribute_names.to_h { |key| [key.to_s, nil] } end end class ArticleEntity < BaseEntity def initialize(article) @article = article end attr_reader :article delegate :title, :body, to: :article def exposed_attribute_names %i[title body] end end
コントローラーでの使い方は次のようになります。
class ArticlesController < ApplicationController def show article = Article.find(params[:id]) render json: ArticleEntity.new(article) end end
とてもシンプルな実装なので個々のエンティティは少し冗長な書き方になってしまうことは否めませんが、その分柔軟でさまざまな状況に対応できます。
元々、この手法を知ったのはSTORES.jpさんのブログ記事でした(多謝!)
追記)
このシリアライザを使っているプロジェクトのメンバーから、JSONをルートプロパティでラップできるようにしたいという要望があり対応しました。
{ title: "Hello!", body: "..." }
のようなJSONをレンダリングするシリアライザオブジェクトが、
{ article: { title: "Hello!", body: "..." } }
のようなJSONを作るようにしたいという要件です。このような場合、ActiveModel::Serializers::JSONにはすでに include_root_in_json というClass Attributeが定義されており、シリアライザのモデル定義の中で self.include_root_in_json = :article のようにしたりすると上記のような要件にも対応できます。今回の場合、これを設定するためのAPI向けシリアライザのドメインを表現するようなメソッドを用意しました。
def self.wrap_exposed_attributes_with(wrap_name) self.include_root_in_json = wrap_name end
次のようにエンティティクラスから使用します。
class ArticleEntity < BaseEntity wrap_exposed_attributes_with :article ... end # ArticleEntity.new(article).as_json #=> { article: { title: "...", body: "..." } }
Updated at