0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rails】APIのJSONレスポンス設計:as_jsonからSerializerへの移行基準

0
Posted at

はじめに

RailsをAPIモード(またはバックエンド)として利用し、フロントエンド(React/Next.jsなど)へデータを返す際、どのようにJSONを整形すべきかは重要な設計判断です。

この記事では、render json: object をそのまま使う場合と as_json を使う場合の違い、そして実務で使われる4つのJSON生成手法(as_json, jbuilder, serializer, fast_jsonapi)の比較について解説します。

この記事でわかる・できること

  • as_json を使うべき理由とセキュリティ上のメリット
  • 実務で使われる主要なJSONシリアライズ手法の比較
  • 自分のプロジェクト規模に合った手法の選び方

この記事の対象者

  • RailsでAPIを開発している初中級者
  • コントローラーのコードが as_json のオプションで肥大化して困っている方
  • 実務レベルのAPI設計指針を知りたい方

動作環境・使用するツールや言語

  • Rails: 7.0以上
  • Ruby: 3.2以上

1. as_json あり・なしの決定的な違い

RailsのコントローラーでJSONを返す際、as_json を使うかどうかの違いは、「返すデータの中身を制御しているか」 です。

ケース①:as_json なし(非推奨)

render json: menus

特徴

ActiveRecordのモデルオブジェクトをそのまま渡すと、全カラムを含んだJSONが生成されます。

  • 問題点1(セキュリティ): 内部的なカラムや見せてはいけない情報まで漏洩するリスクがある
  • 問題点2(保守性): DBにカラムを追加すると、意図せずAPIのレスポンスが変わってしまう
  • 問題点3(関連データ): includes していても、明示しない限り関連モデル(memberなど)が含まれないことがある

ケース②:as_json あり(推奨)

render json: menus.as_json(
  only: [:id, :name],
  include: { member: { only: [:id, :name] } }
)

特徴

必要なカラムと関連データを明示的に指定します。

  • メリット: セキュアで、通信量が少なく、API仕様として堅牢になる
  • デメリット: コントローラーにロジックを書くため、複雑になると可読性が下がる

API設計としては、必ず 出力データを制御する(ケース②) の方が正解です。

2. Rails APIにおける4つのJSON生成手法比較

実務では as_json 以外にもいくつかの選択肢があります。プロジェクトのフェーズに合わせて選択しましょう。

手法比較マップ

手法 特徴 向き・不向き
1. as_json 最も手軽。ActiveRecord標準機能。 小規模・プロトタイプ向け。複雑になると辛い。
2. jbuilder Viewテンプレートのように書ける。 かつての主流。処理速度が遅めなのが難点。
3. Serializer クラスでJSON構造を定義。責務分離が綺麗。 中規模以上の標準。 保守性が高い。
4. jsonapi-serializer 超高速。大規模データ向け。 大規模API向け。小規模にはオーバースペック。

各手法の詳細

① as_json

コントローラー内で完結するため導入が楽ですが、ロジックが分散しやすく再利用性が低いです。

② jbuilder

app/views/menus/index.json.jbuilder のようにView層で定義します。Rails標準ですが、パフォーマンス面で敬遠される傾向にあります。

③ Serializer (ActiveModel::Serializer など)

JSONの構造定義を専用のクラス(Serializer)に切り出します。

app/serializers/menu_serializer.rb
class MenuSerializer < ActiveModel::Serializer
  attributes :id, :name
  belongs_to :member
end

コントローラーは render json: menus と書くだけで、自動的にこのクラスが適用されます。再利用性が高く、テストも書きやすいため、実務での採用率が最も高い手法です。

④ fast_jsonapi (現 jsonapi-serializer)

Netflixが開発した高速ライブラリ(の派生)です。大量のデータを扱うAPIや、パフォーマンスが最優先される場合に採用されます。

3. 実務での選び方(結論)

プロジェクトの規模やフェーズによって最適な選択肢は異なります。

個人開発・プロトタイプ(MVP)

  • as_json でOKです。 まずはスピード優先で作る場合に適しています

中規模・継続的な開発(サービス開発)

  • Serializer 一択です。 APIのエンドポイントが増え、関連データ(Member, Menu, Invitationなど)が複雑になってくる段階では、as_json では管理しきれなくなります。保守性を高めるためにSerializerを導入しましょう

大規模・公開API

  • jsonapi-serializer パフォーマンスチューニングが必須となるフェーズで検討します

4. as_jsonからSerializerへの移行方法

Railsのデファクトスタンダードである active_model_serializers gem を使用する例で進めます。

手順1: Gemのインストール

まず、Gemfile に以下を追加して bundle install します。

Gemfile
gem 'active_model_serializers', '~> 0.10.0'
bundle install

手順2: Serializerファイルの作成

Railsのジェネレータを使って、モデルに対応するSerializerを作成します。
例として Menu モデル(id, name カラムと Member への関連を持つ)で考えます。

rails g serializer Menu

これで app/serializers/menu_serializer.rb が作成されます。

手順3: Serializerの実装

as_json で指定していた内容を、Serializerクラスに移し替えます。

移行前(Controllerでの as_json):

app/controllers/menus_controller.rb
render json: @menus.as_json(
  only: [:id, :name],
  include: {
    member: { only: [:id, :name] }
  }
)

移行後(Serializerの実装):

app/serializers/menu_serializer.rb
class MenuSerializer < ActiveModel::Serializer
  # 返したいカラムを指定(only: ... の代わり)
  attributes :id, :name

  # 関連モデルを指定(include: ... の代わり)
  belongs_to :member
  
  # カスタム属性(as_jsonではメソッド定義が必要だったものも簡単に書ける)
  attributes :display_text
  
  def display_text
    "#{object.name} (ID: #{object.id})"
  end
end

関連する MemberSerializer も必要になるので作成します。

rails g serializer Member
app/serializers/member_serializer.rb
class MemberSerializer < ActiveModel::Serializer
  attributes :id, :name
end

手順4: Controllerの修正

Controller側は、オプション指定を削除してシンプルにします。Gemを入れたことで、render json: オブジェクト とするだけで自動的に対応するSerializerが使われるようになります。

修正後のController:

app/controllers/menus_controller.rb
class MenusController < ApplicationController
  def index
    @menus = Menu.all.includes(:member) # N+1対策は忘れずに
    
    # .as_json(...) のオプションは全て不要になる
    render json: @menus
  end
end

【応用】特定の場面だけでSerializerを使い分けたい場合

例えば、「一覧画面(index)」と「詳細画面(show)」で返すJSONの中身を変えたい場合があります。

その場合は、別のSerializerを作成して、Controllerで明示的に指定します。

1. 詳細用Serializerを作成

app/serializers/menu_detail_serializer.rb
class MenuDetailSerializer < ActiveModel::Serializer
  attributes :id, :name, :description, :created_at # 詳細なので項目多め
  belongs_to :member
  has_many :ingredients # 材料なども含める
end

2. Controllerで指定

app/controllers/menus_controller.rb
def show
  @menu = Menu.find(params[:id])
  
  # serializer: オプションでクラスを指定
  render json: @menu, serializer: MenuDetailSerializer
end

移行のメリット再確認

  1. Controllerがスッキリする: 表示ロジックが消え、本来の処理に集中できる
  2. 再利用できる: 他のControllerで Menu を返す時も、自動で同じJSON形式になる
  3. 関連の制御が楽: belongs_to や has_many を書くだけで、ネストしたJSONが綺麗に作れる

まとめ

Rails APIのレスポンス設計において重要なポイントは以下の通りです。

  1. 生データ(render json: object)は避ける: 必ず出力データを制御する
  2. 小規模なら as_json: 手軽に制御可能
  3. 中規模以上なら Serializer: JSON構築ロジックをクラスに切り出し、保守性を確保する

自分のプロジェクトが「コントローラーの見通しが悪くなってきた」と感じたら、Serializerへの移行タイミングです。

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?