【Rails】modelやhelperに無駄なmethodを増やさないpresenter

`Presenter`という層を導入してみて、良かったなあってこと、書こうと思います🧸  
正直 n番煎じで、どこにでもある記事なのですが、`Presenter`を使う時のルールから考えれば、感じたメリットがやや違うなあと思ったので自分なりに書きます🐲
（実は、導入したのは弊社の別のエンジニアなのですが、もういないので勝手にいただきます😏）

## こんなRailsの開発を回避したい！
そもそも、どういう開発で`Presenter`が使われるのか？
僕なりに、問題提起していこうかと思います。

### Viewのロジックをどんどんモデルに追加してしまう
厳密には以下のようなことはありえないですが、
それ`view`以外じゃ使わないでしょう的なことを察してほしいです。🤫

```.rb
class User < ApplicationRecord
  def self.name_array(users)
    users.map { |e| e.name }
  end
end
```

以下の通りですが、viewのロジックばっかりのモデルは少し嫌ですね。

> モデルとは、データベースや外部サービスへのアクセスをはじめ、ビジネスロジック全般を担当するコンポーネントのことです。
> 出典 : [モデルの基本](https://www.buildinsider.net/web/rubyonrails4/0204)


### controllerのアクションmethod の外でインスタンス変数を作ってしまう
別に問題はないけど「え、こんなインスタンス作ったっけ？」ってなります。
（気づかずに同じインスタンスを作ってしまうとかも嫌ですね。）

```.rb
class UsersController < ApplicationController
  before_action :set_greate_users

  def index
    @target_users = current_user.target_users
  end

  private

  def set_greate_users
    @great_users = current_user.great_users
  end
end
```

以下の記事でも、謎の場所でインスタンスを定義するな！と言ってる過激派もいます。
[Controller Best Practices: Don’t Hide Instance Variables](https://blog.codeminer42.com/controller-best-practices-dont-hide-instance-variables-5e8bf067156/)

### 神helperにしてしまう
そもそもviewのロジックはhelperに追加しようというのが基本的だと思うのですが、危険性を書きます。

- helperは、どこでも読み込まれるので、メソッド名が衝突する可能性がある
- helperの修正を行うと、意図しない箇所も変更されてしまう
- 色々なhelperから呼び出されると、ロジックを追うのだけで大変になる

```.rb
module UserHelper
  def great_users
    current_user.great_users
  end
end
```

ほぼ以下の記事の受け売りなのは秘密です。
[Decorator と Presenter を使い分けて、 Rails を ViewModel ですっきりさせよう](https://tech.kitchhike.com/entry/2018/02/28/221159)

## なぜそんな開発になったの？
僕も開発していて、ハッとしたので、書き残します。

### rubocopのAbcSizeが怒るから
ありがちですね。
そもそもアクションmethodを起点に何かが起こるので、アクションmethod内で色々したくなります。
でも、ロジックが多く書けない以上、インスタンス変数から呼び出せるようにモデルにロジックを追加するしかない気持ちもわかります。

```sh
Metrics/AbcSize: Assignment Branch Condition size for show is too high. [16.31/15]
```

`AbsSize`を追加されたプルリク
- [New cop Metrics/AbcSize](https://github.com/rubocop-hq/rubocop/pull/1370)

`AbcSize`のデフォルトを考え直そうという提案がされたプルリク
- [Relax metrics cops threshold](https://github.com/rubocop-hq/rubocop/pull/4588)
↓ちゃんと明文化することが大事って書いてありました。
> I think that at this point is way more important to document some of the defaults and why they were picked than relaxing them. Not to mention that default values like 18 and 14 seem extremely random.

### rails_best_practicesに言われたから
`rails_best_practices`がアクションmethod内部で変数に対して4回以上methodを呼び出すと、以下のようなエラーが出力されます。
2~3個のインスタンスを作成していたら、確かに出がちなエラーだと思います。

```sh
/Users/.../app/controllers/users_controller.rb:5 - 
move model logic into model (current_user use_count > 4)
```

## Presenterを導入しよう
ここからは n番煎じ、presenterの導入方法なので、どの記事を読んでも同じです。
他の記事読んでも大差ないです。

### Presenterを使う時のマイルール
何でも`Presenter`を使えってことではありません。
僕なりの使い方を書きます。

- インスタンス変数が4個以上になるなら`Presenter`  
*4という数字に意図はありません、ノリです。
- どこでも使う`view`のロジック（URLの置換）などは`helper`を使う
- `Decorator`という考え方は入れない(混乱するので)

### Presenterのコード
弊社では`Presenter`にいくつもの引数を渡せるように、`BasePresenter`を作っています。

```app/presenters/base_presenter.rb
require 'delegate'

class BasePresenter < SimpleDelegator
  attr_reader :resource, :args

  def initialize(resource, **args)
    @resource = resource
    @args = args
    super(resource)
  end
end
```

クラス名は、どのコントローラーのどのアクションmethodなのかわかりやすいようにしています

```app/presenters/users/index_presenter.rb
module Users
  class IndexPresenter < BasePresenter
  # arg[:taregt_id]で、第2引数のhashの中身を呼び出せます
  # ↓ロジックを書く
  def target_name
    User.find(arg[:target_id]).name
  end
  
  alias_method :current_user, :resource
　# ↑resourceでも呼び出せますが、current_userで呼び出せるようにしています
  end
end
```

controllerで呼び出すだけです

```.rb
class UsersController < ApplicationController
  def index
    @presenter = Users::IndexPresenter.new(current_user, target_id: params[:id])
  end
end
```

あとは`@presenter.target_name`で呼び出せます！

## DecoratorとPresenterの違いって？
僕もよくわかりません😳
あえて載せませんが、`rails presenter`で検索してヒットする記事は全て`Decorator/Presenter`の解釈が違いました。
弊社では [Decorator/Presenter](https://www.oiax.jp/rails/tips/decorators_and_presenters.html) の記事に書いてあった [Exhibit vs Presenter](http://mikepackdev.com/blog_posts/31-exhibit-vs-presenter) が一番近いなと思いました。

> Decorator パターンは定義上、1個のオブジェクトしか内包しないので、「Presenter」のように2個のオブジェクトに委譲を行うような設計は厳密には Decorator パターンではありません。

弊社では`Presenter`に`current_user`という基本的なオブジェクトと、その他のhashを渡しています。
内部で複数のインスタンス変数が定義されたりするので、ちょっと特殊な使い方です。

## 所感
そういえば、`Presenter`を導入した記事を読むと、昔の人は`rails`で`html`を返却していたみたいですね。
弊社は`rails`は`json`を返却するAPIで、`Vue`でフロントを制御しています。
昔の人はどんな開発をしていたんだろうか、、、あまり想像できていない🙄🙄