decorator/presenterの概念を理解してRailsをスリムにする

肥大化したRailsのリファクタリングをしていますが、一部のcontrollerやmodelに関しては処理が多くなり限界を感じつつありました。

そこでdecoratorやpresenterを活用してみようと思ったものの、この2つの違いなどがいまいち分かりづらく感じました。色々読んでみて知ったそれぞれの違いや役割について整理してみました。

## decorator/presenterとは?
Decoratorはソフトウェアのデザインパターンの一つです。GoFによって定義されたそうです。

https://ja.wikipedia.org/wiki/Decorator_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3

Presenterは元々はMVCから派生したアーキテクチャパターンのMVP(Model-View-Presenter)の考えからきた概念のようです。Controllerの肥大化防止、テスタビリティの向上が期待できるみたいです。

https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter

## decoratorとpresenterの役割
下記の記事では、あくまでチーム内での定義を書いてくださってますが非常にわかりやすかったので引用させていただきました。

>
* Decorator
    * 単一のモデルクラスに対応する ViewModel
* Presenter
    * 複数のモデルクラスにまたがる ViewModel
    * 永続化されたモデルと一致しない ViewModel
* ViewModel
    * `Decorator` 、`Presenter` の上位概念。ビューに関連するロジックをまとめるレイヤーを指す。

https://www.google.com/amp/s/tech.kitchhike.com/entry/2018/02/28/221159%3Famp%3D1

`Decorator`と`Presenter`の違いは単一のモデルを扱うか複数のモデルを扱うかどうかです。また、永続化されないケースやActiveRecord化されていないモデルなんかもPresenterで扱うそうです。

`ViewModel`については`View Object`とか`Presenter層`と表現されることも多いですが、**ビューに関連するロジックをまとめるレイヤー**と解釈すれば問題ないかと思います。

## decoratorについて
decoratorは単一のモデルクラスに対応するものです。例えば、userモデルがあった場合、そのうちビューに関連するロジックをまとめる場合は`user_decorator`を作り、ここに寄せていくような形です。

以下はdraperからの引用です。

>
```ruby:app/helpers/articles_helper.rb
# articleインスタンスのpublishedに応じて表示の出し分けをするhelper
def publication_status(article)
  if article.published?
    "Published at #{article.published_at.strftime('%A, %B %e')}"
  else
    "Unpublished"
  end
end
```

上記のようなものが**ビューに関連するロジック**といえます。こうした処理をモデルに書いているパターンもあるかもしれませんが、これらもdecoratorにまとめることができます。

>
```ruby:app/decorators/article_decorator.rb
class ArticleDecorator < Draper::Decorator
  delegate_all
  def publication_status
    if published?
      "Published at #{published_at}"
    else
      "Unpublished"
    end
  end
  def published_at
    object.published_at.strftime("%A, %B %e")
  end
end
```

あとはcontrollerでdecoratorを呼び出します。

>
```ruby:app/controllers/articles_controller.rb
def show
  @article = Article.find(params[:id]).decorate
end
```

このようにdecoratorを使うことでViewに関連したロジックをまとめることができます。

### どうやって実装するのか？
`draper`や`active_decarator`などのgemが有名です。また、かんたんなdecoratorであればPOROで定義している方もいるみたいです。

https://github.com/drapergem/draper

https://github.com/amatsuda/active_decorator

両方使っているわけではないのでかなり粗いですが、大まか違い下記です。

* `ActiveDecorator`はViewでActiveRecordを取り扱うときに暗黙的にデコレートしたオブジェクトが渡される
* `Draper`は明示的にActiveRecordインスタンスのあとに`.decorator`を宣言する必要がある

これらの詳細について他の記事もたくさんあるので詳細は割愛します。

https://nishisuke.hateblo.jp/entry/2018/01/01/215459

ちなみに自分はプロジェクトの規模感や明示的にdecoratorを使っていることがわかりやすいという点からdraperを導入しました。プロジェクト規模やチームに合わせて導入してみてください。

### helperじゃだめなのか?
viewのロジックを担うという点ではRailsではhelperの役割では？と思うかもしれません。

ただ、helperは名前空間がグローバルなため、他のhelperと名前が重複しないよう注意する必要があります。

```ruby
class UserHelper
  def full_name
    #~~~
  end
end

class ShopHelper
  def full_name
    #~~~
  end
end
```
また、本来userインスタンスを想定しているところにshopインスタンスをいれてしまう、といったトリッキーなこともできてしまう恐れがあります。

モデルに依存しないような汎用的な処理であればhelperに持たせても良いですが、モデルに依存するような形なのであれば、decoratorにまとめるほうが安全かつ明確です。

### decoratorを取り扱う上での注意事項
かといって、なんでもかんでもhelperのロジックをdecoratorに入れるのはアンチパターンです。オブジェクト指向の原則に従って適切に切り分けることが重要です。

>
Decoratorを従来のビューヘルパー代わりに用いることは避けましょう。Decoratorからコードの臭いが立ち昇ったら適切に処理し、メソッドや責務が複雑にならないようにしましょう。Decoratorクラスを新しく導入することを恐れてはいけませんが、Decoratorが存在してもよい正当な理由がある場合にのみ行いましょう。

https://techracho.bpsinc.jp/hachi8833/2018_03_06/53535


## presenterについて
decoratorに比べるとRailsで取り扱われている例が少ないかもしれませんが、contorollerを薄くしたい場合に重宝しています。

自分はPOROでpresenterクラスを作成し、こちらをcontrollerで生成する形で取り入れています。この方法であればgemなどを入れる必要もないので簡単に試すことができます。

まず、`app/presenters`などをつくりpresenterクラスを定義します。

```ruby:app/presenters/hoge_presenter.rb
# POROで実装
class HogePresenter
  def initialize(hoge_id)
    @hoge = Hoge.find(hoge_id)
  end

  def fuga
    @hoge.xxx 
  end
end
```

controllerでpresenterのみを呼び出します。

```ruby:app/controller/hoge_controller.rb
class HogeController < ActionController::Base
  def index
    @presenter = HogePresenter.new(params[:hoge_id])
  end
end
```

下記のように`@presenter`からViewのロジックを呼び出します

```erb:app/views/hoge.html.rb
<%= @presenter.fuga %>
```

このようにすることで、controllerの処理をほとんどpresenterにまとめることができます。また、presenterに分離されたことで、テスタビティも向上します。

### インスタンス変数やset_*メソッドを減らせる
Rails Wayに沿って愚直に開発していると場合よってはcontrollerにはインスタンス変数が増えていきます。また、`set_xxx`といったprivateメソッドをbefore_actionで呼びまくるみたいなことにもなりがちです。

下記のように懐疑的に思う人もちらほら見受けられました。

https://osa.hatenablog.com/entry/good-bye-before-action-setter

自分たちのプロジェクトでもbefore_actionに`set_`しまくって明らかに可読性が下がっている箇所があったりしました。（Rubocopに従っているとなりがちです）

しかし、presenterであれば上記のインスタンス変数も1つになり、可読性の向上につながります。

### presenterの利点まとめ
* インスタンス変数やsetter系のメソッドが減り、controllerを薄くすることができる
* テスタビリティが高い
* POROでもはじめられるので参入障壁が低い


## まとめ
以上、**「decorator/presenterの概念を理解してRailsをスリムにする」**でした!

* decoratorもpresenterもビューに関連するロジックをまとめるレイヤーを指す
* Decoratorはソフトウェアのデザインパターンの一つで単一のモデルクラスに対応し、ヘルパーやモデルの肥大化防止に役立つ
    * helperよりも安全に扱いやすい
    * Railsで扱いやすいgem(daraperやActiveDecorator)がある
* Presenterは複数のモデルクラスにまたがり、controllerの肥大化やテスタビティの向上が期待できる
    * インスタンス変数やset_*メソッドが減らせる
    *  POROで簡単に始められるので参入障壁が低い

decoratorやpresenterの理解が少しでも進んだなら幸いです。



## 参考文献
https://codeclimate.com/blog/7-ways-to-decompose-fat-activerecord-models/

https://techracho.bpsinc.jp/hachi8833/2017_11_20/48322

https://techracho.bpsinc.jp/hachi8833/2021_01_07/14738

https://www.oiax.jp/rails/tips/decorators_and_presenters.html

https://morizyun.github.io/blog/draper-ruby-gem-code-clear/