1.はじめに
共同開発中にメンターさんから、「Decorator
について調べてみて」とアドバイスを頂きました。
実装したい内容が実現できて安心してしまっていたので、「コードは動けば良いわけではないんだな...」と実感しました。
今回はDecoratorについて調べてみました。
2.環境
- mac.os バージョン10.15.6
- Ruby 2.7.2
- Rails 6.1.3.1
- psql (PostgreSQL) 12.6
3.MVCについて
冒頭でも少し触れましたが、メンターさんにDecoratorについてご紹介頂いたのは、「ビューのロジックに関することを、コントローラで定義してコードを書いていたから」だと思いました。
def index
#動画の一覧を昇順(1,2,3...)にする
@movies = Movie.order(created_at: :asc)
#動画の番号を表示する(ために必要な変数を定義) ←ビューのロジックに関すること
@base_number = ...
end
どこにコードを書くのか曖昧だと感じたので、まずMVCについて復習も兼ねて書いていきます。
3.1 モデル(M)
データとデータに関わるビジネスロジック(アプリケーション特有の処理)をオブジェクトとして実装したもの。データベースの保存や読み込みを行う。
※オブジェクト:関連する変数(値)とメソッド(動作)をまとめて、そのまとまりに名前を付けたもの。
3.2 ビュー(V)
ブラウザに表示する画面、すなわちHTMLなどのHTTPレスポンスの中身を実際に組み立てる部分。必要に応じてコントローラからモデルなどのオブジェクトなどを受け取り、画面表示に利用する。
3.3 コントローラ(C)
ユーザーが操作するブラウザなどのクライアントからの入力(リクエスト)を受け、適切な出力(レスポンス)を作成するための制御を行う。MとVのコントロールを行う。
4.Decoratorとは
原則として、モデルにはビジネスロジック、ビューではプレゼンテーション(見た目の)ロジックを記述するとした時に、「モデル固有のプレゼンテーションロジックはどこに書けばいいのか?」
という問題が発生します。
ビューに書いた場合:モデル固有の処理なのに離れて分散してしまう。重複が発生しやすくなる。
モデルに書いた場合:コードの再利用性やメンテナンス性を損なう恐れがある。モデルが太る。
そこでDecoratorパターンを使うと、モデルごとや利用シーンごとにプレゼンテーションレイヤーの実装をまとめることができます。
アプローチの例
パターン1:モデルオブジェクトをインスタンス変数として保持し、ビュー用の処理を閉じ込めたクラスを作成して利用する
例:「アイスクリームのトッピングの処理のクラスを作り、トッピングクラスのインスタンス変数にアイスクリームのインスタンス変数を持たせる」ことでトッピングがのったアイスクリームを表現する。
パターン2:ビュー用の処理をモジュールとして定義し、ビューの文脈でモデルオブジェクトをextendして機能追加する
モジュール:一連の振る舞いの設計図を一箇所にまとめたもの。
モジュールはオブジェクトを生成できず、includeメソッドを使ってクラスに取り込んで使用します。
include
はクラス(のインスタンス)に機能を追加しますが、extend
はある特定のオブジェクトだけにモジュールの機能を追加したいときに使用します。
class VanillaIcecream
# CashewNutsToppingIcecreamというモジュールを取り込みたい
include CashewNutsToppingIcecream
end
パターン3:DraperやActiveDecoratorのgemを利用する
gemを導入し、decoratorファイルに記述するという方法です。
# Gemfileに記述
gem 'draper'
①gemを導入し、bundle install(またはbundle)
を実行します。
②次に、
# generateはgでも可
# ターミナルで実行する
rails generate draper:install
rails generate decorator movie(モデル名)
を順に実行します。
③decorator.rbにコードを記入します。
class movieDecorator < Draper::Decorator
delegate_all # movie(モデル)のメソッドを全て呼び出せる
def base_number(メソッド名)
# モデルのメソッドを使用することもできる
end
end
④decoratorを用いたコードに書き換えます。
(1)コントローラ
def index
#動画の一覧を昇順(1,2,3...)にする
@movies = Movie.order(created_at: :asc)
#動画の番号を表示する(ために必要な変数を定義) ←ビューのロジックに関すること
@base_number = ...
end
書き換えた後(decoratorインスタンスを作成)
def index
# 複数のオブジェクトの場合はdecorate_collectionを使用する
@movies = MovieDecorator.decorate_collection(Movie.order(created_at: :asc))
end
(2)ビュー
<% @movies.each.with_index(1) do |movie, i| %>
<p class="movie-title">
No.<%= @base_number + i %>:<%= movie.title %>
</p>
<% end %>
書き換えた後(decoratorメソッドを呼び出す)
<% @movies.each.with_index(1) do |movie, i| %>
<p class="movie-title">
No.<%= movie.base_number + i %>:<%= movie.title %>
</p>
<% end %>
おまけ
draperやactive_decotratorを調べている際、「ヘルパーと何が違うのか?」混乱してきたので調べたところ、こちらに違いがありました。特定のモデルに関連している
ビューの描写に関するものはdecoratorということですね。
5.まとめ
「モデル固有のプレゼンテーションロジックはどこに書けばいいのか?」
という問題が発生した時に、「Decoratorパターンを使い、そこに記述しましょう」
ということですね。
モデル→decorator→ ビュー
とすることでモデルに関連したビューの表示をすることができました。
6.参考
1.大場寧子他, 現場で使えるRuby on Rails5速修実践ガイド, マイナビ出版, 2018.
2.Rubyのオブジェクトとは
3.draper
4.ActiveDecorator
5.decoratorを導入して、viewの記述をすっきりさせ、modelの肥大化を回避する【Day 3/30 2nd】
6.Rails Viewの表示のためにDecoratorを用意してHelperとModelを助ける
7.12. Decorator パターン
8.instance method Object#extend
9.Decoratorの役割とDraperについて
10.【入門】Draperを使ってみる
7.最後に
記事の感想や意見、ご指摘等あれば伝えていただけるとありがたいです。
読んでいただき、ありがとうございました。