要約
- コントローラー用の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アプリケーション向けの実装では、DraperやActiveDecoratorが知られています。特に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