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

# 要約
- コントローラー用のConcernを作って、デコレートメソッドの呼び出しを短く
- デコレーターにas_jsonメソッドを定義して、コントローラーの見た目を簡潔に

```rb
class IssuesController < ApplicationController
  include DecoratorAction

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

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

- 数字と文字列の変換
- 真理値と0/1の変換
- 日付フォーマットの変更

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

# 目的

このような場合、[Decoratorパターン](http://www.techscore.com/tech/DesignPattern/Decorator.html/)を使うのが一般的です。

Railsアプリケーション向けの実装では、[Draper](https://github.com/drapergem/draper)や[ActiveDecorator](https://github.com/amatsuda/active_decorator)が知られています。特にActiveDecoratorがシンプルで好きです。

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

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

```rb
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のレンダリングにビューを使わない場合、

```rb
class IssuesController < ApplicationController
  def show
    render json: Issue.find_by(id: params[:id]), except: %i[id created_at updated_at]
  end
end
```
decoratorへ自動変換されません。

[^1]: https://github.com/amatsuda/active_decorator/blob/master/lib/active_decorator/monkey/abstract_controller/rendering.rb


## 対策
### コントローラーで手動デコレーション
`issues_controller.rb`で取得したオブジェクトをデコレーションします。

```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`を作る

```rb
module DecoratorAction
  extend ActiveSupport::Concern

  private

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

#### Concernを使う

```rb
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](https://stackoverflow.com/questions/2572284/how-to-override-to-json-in-rails)

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

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

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

## 最終形

```rb
class IssuesController < ApplicationController
  include DecoratorAction

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