Rails
JSON
REST-API
activedecorator

RailsのREST APIサーバーのレスポンス整形にActiveDecoratorを使う

More than 1 year has passed since last update.


要約


  • コントローラー用のConcernを作って、デコレートメソッドの呼び出しを短く

  • デコレーターにas_jsonメソッドを定義して、コントローラーの見た目を簡潔に

class IssuesController < ApplicationController

include DecoratorAction

def show
render json: deco { Issue.find_by id: params[:id] }
end
end


背景

REST APIサーバを作っている時に、返却するJSONをモデルそのままではなく整形する必要があることがあります。例えば、


  • 数字と文字列の変換

  • 真理値と0/1の変換

  • 日付フォーマットの変更

などです。整形処理はアプリケーションのプレゼンテーションに関わることです。モデルクラスには書きたくありません。


目的

このような場合、Decoratorパターンを使うのが一般的です。

Railsアプリケーション向けの実装では、DraperActiveDecoratorが知られています。特にActiveDecoratorがシンプルで好きです。

ActiveDecoratorを使って、JSONの整形処理をモデルクラスからDecoratorクラスに分離したいです。


例題 「真理値を1/0に変換」

issue_decorator.rbに、でclose_flgを真理値から、数値の1/0に変換します。

module IssueDecorator

def close_flg
super ? 1 : 0
end
end

close_flgメソッドを上書きすると実現できます。


課題1 JSONをレンダリングするときに自動的にデコレートされない

ActiveDecoratorはAuto-decorating via renderします。具体的にはAbstractController::Rendering#view_assignsの実行時に、モデルのデコレーションを行います1

REST APIサーバーで、特に次のようにJSONのレンダリングにビューを使わない場合、

class IssuesController < ApplicationController

def show
render json: Issue.find_by(id: params[:id]), except: %i[id created_at updated_at]
end
end

decoratorへ自動変換されません。


対策


コントローラーで手動デコレーション

issues_controller.rbで取得したオブジェクトをデコレーションします。

class IssuesController < ApplicationController

def show
issue = Issue.find_by id: params[:id]
decorator = ActiveDecorator::Decorator.instance.decorate(issue)
render json: decorator, except: %i[id created_at updated_at]
end
end

全てのコントローラーでデコレーション用のメソッドを呼ぶのは不便です。


デコレーションメソッドをConcernとして定義

Concernにメソッド定義して、includeで使いまわせるようにしましょう。


Concernを定義

decorator_action.rbを作る

module DecoratorAction

extend ActiveSupport::Concern

private

# デコレーターを適用
def deco
ActiveDecorator::Decorator.instance.decorate yield
end
end


Concernを使う

class IssuesController < ApplicationController

include DecoratorAction

def show
render json: deco { Issue.find_by id: params[:id] }, except: %i[id created_at updated_at]
end
end

ActiveDecoratorの呼び出しが短くなって、うれしいですね。


課題2 デコレーターで情報を減らせない

デコレーターでは情報の変更と追加はできます。減らすことができません。

ActiveRecordオブジェクトが持っている


  • id

  • created_at

  • updated_at

の情報を、APIのレスポンスから除外したい時は、renderメソッドのexceptオプションを指定して除外します。

render json: deco { Issue.find_by id: params[:id] }, except: %i[id created_at updated_at]

DBから取得したモデルを、そのまま返すだけのAPIはもう少しシンプルに書きたいです。


対策


デコレーターでas_jsonメソッドを定義

ActiveRecordオブジェクトのJSON表現を変更したいときは、as_jsonメソッドをオーバーライドします。

json - How to override to_json in Rails? - Stack Overflow


You should override as_json in your Model to create the JSON structure you want. as_json


issue_decorator.rbは次のようにします。

module IssueDecorator

def as_json(_)
{
title: title,
description: description,
close_flg: close_flg ? 1 : 0,
}
end
end


最終形

class IssuesController < ApplicationController

include DecoratorAction

def show
render json: deco { Issue.find_by id: params[:id] }
end
end