7
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-05-22

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

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

decorator/presenterとは?

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

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

decoratorとpresenterの役割

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

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

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

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

decoratorについて

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

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

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にまとめることができます。

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を呼び出します。

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

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

どうやって実装するのか?

draperactive_decaratorなどのgemが有名です。また、かんたんなdecoratorであればPOROで定義している方もいるみたいです。

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

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

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

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

helperじゃだめなのか?

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

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

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が存在してもよい正当な理由がある場合にのみ行いましょう。

presenterについて

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

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

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

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のみを呼び出します。

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

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

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

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

インスタンス変数やset_*メソッドを減らせる

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

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

自分たちのプロジェクトでも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の理解が少しでも進んだなら幸いです。

参考文献

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2