😉

Decorator/Presenter を作成し、ビューのロジックをリファクタリング

2024/08/12に公開

背景

今回 Decorator/Presenter について学習する機会がありました。

存在自体は知っていたのですが、あまり深く学習したことがなかったので、知識を定着させるため、Decorator/Presenter の特徴と使い方をわかりやすくまとめてみました。

よかったら参考にしていただけると幸いです。

Decorator/Presenterとは?

Decorator や Presenter は特定のモデルがもつ属性やそのロジックを使用し、表示(view)に関するロジックを実装するオブジェクトのことです。

ビューで使用するメソッドは、ヘルパーに記述するのが慣例です。

しかし、適切に設定しないと、特定のコントローラだけでなく、すべてのコントローラでヘルパーに定義したメソッドを使用することができてしまいます、

さらに、きちんと適切に責務(役割)を分離させないと FatModel,FatController になってしまいます。

この問題を解決することができるのが、Decorator や Presenter です。

また、ビューの表示ロジックを単一モデルの表示ロジックとして使用するときは Decorator を使用し、複数モデルに関連する表示ロジックを実装するときは、Presenter を使用することで、責務を分離することができます。

https://engineer-daily.com/ruby-on-rails-design-pattern/#Presenterパターン

https://tech.kitchhike.com/entry/2018/02/28/221159

実際に Decorator と Presenter を用いて view の表示ロジックを持つメソッドを作成してみました。

Decorator の作成

今回は、Decorator を作成するために ActiveDecorator というgemを使用して実装してみました。

ドキュメントを参考にすると、 Decorator は下記の手順で作成することができます。

  1. Gemfileに gem 'active_decorator’ を記載し、bundle install コマンドを実行
  2. rails g decorator user コマンドを実行し、UserDecorator を作成

今回、Decoratorを試しに使用してみたいので、練習としてUser,Companyという各モデルに post_code という郵便番号を表すカラムを用意しました。

そして、それを用いて view の表示ロジックを Decorator で実装してみたいと思います。

まず、Decorator を使用する前のコードが下記になります。

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    @users = User.all
  end

  private
  def users_params
    params.require(:user).permit(:post_code)
  end
end

# app/views/users/index.html.erb
<table>
  <tr>
  <% @users.each do |u| %>
    <td><%= u.id %></td>
    <td><%= u.first_name %></td>
    <td><%= u.last_name %></td>
    <td><%= u.post_code.present? ? u.post_code.to_s.insert(3, '-') : "" %></td>
  <% end %>
  </tr>
</table>

上記のコードでは、Userモデルの各データを表示しています。

ただ、<%= u.post_code.present? ? u.post_code.to_s.insert(3, '-') : "" %> という箇所は、データをそのまま表示させるのではなく、post_code があるかないか判定してから、左から3桁目に対して「-」を追加し、表示させるロジックを行っています。

上記の表示ロジックを Decorator を作成し、責務(役割)を分離させます。

作成した Decorator と その Decorator に記載したメソッドを呼び出す箇所を書き換えると、下記のようになります。

# app/decorators/user_decorator.rb
module UserDecorator
 def formatting_post_codes
   post_code.present? ? post_code.to_s.insert(3, '-') : ""
 end
end

# app/views/users/index.html.erb
<table>
  <tr>
  <% @users.each do |u| %>
    <td><%= u.id %></td>
    <td><%= u.first_name %></td>
    <td><%= u.last_name %></td>
    <td><%= u.formatting_post_codes %></td>
  <% end %>
  </tr>
</table>

これで、view の表示ロジックをうまくリファクタリングすることができました。

ただ、Companyモデルにも post_code カラムがあり、同じようにデータを表示させたい時、再度、Companyモデルの Decorator を作成し、formatting_post_codes メソッドと同じ処理を書くと、少し冗長になってしまいます。

そこで、Presenter を使用することで、複数モデルに共通する view の表示ロジックを1箇所にまとめることができます。

Presenter の作成

Presenter は app/presenters 配下に post_code_formatter.rb を作成しました。

行ったこととしては、post_code_formatter.rb に先ほど作成した formatting_post_codes メソッドを記述し、1箇所にまとめ、UserDecorator や CompanyDecorator といったDecorator から include し、view側でそのメソッドを呼ぶようにしました。

作成したコードが下記になります。

# app/presenters/post_code_formatter.rb
module PostCodeFormatter
  def formatting_post_codes
    post_code.present? ? post_code.to_s.insert(3, '-') : ""
  end
end

# app/decorators/user_decorator.rb
module UserDecorator
  include PostCodeFormatter
end

# app/decorators/company_decorator.rb
module CompanyDecorator
  include PostCodeFormatter
end

# app/views/users/index.html.erb
<table>
  <tr>
  <% @users.each do |u| %>
    <td><%= u.id %></td>
    <td><%= u.first_name %></td>
    <td><%= u.last_name %></td>
    <td><%= u.formatting_post_codes %></td>
  <% end %>
  </tr>
</table>

# app/views/companies/index.html.erb
<table>
  <tr>
  <% @companies.each do |u| %>
    <td><%= u.id %></td>
    <td><%= u.first_name %></td>
    <td><%= u.last_name %></td>
    <td><%= u.formatting_post_codes %></td>
  <% end %>
  </tr>
</table>

上記のコードで、うまく実装することができました。

まとめ

今回、Decorator や Presenter を用いて、view の表示ロジックをリファクタリングしてみました。

今回学習してみて、helper との違いや Decorator や Presenter に view の表示ロジックを知ることができました。

今までは view の表示ロジックを model にのみ記載してしまっていたので、今後は今回学習した Decorator や Presenter を使用し、責務(役割)を適切に分けていきたいと思います。

参考

https://github.com/amatsuda/active_decorator

https://engineer-daily.com/ruby-on-rails-design-pattern/#Presenterパターン

https://tech.kitchhike.com/entry/2018/02/28/221159

Discussion