【Railsデザインパターン①】Decorator
綺麗なコードを書きたい!!!
こんにちは、大ちゃんの駆け出し技術ブログです。
みなさん、綺麗なコード書きたくないですか??
最近自分はポートフォリオをリリースしまして、リリース後のユーザーの反応から機能などを追加しているのですが、まあコードがぐちゃぐちゃしていて追加が難しい。「これなんのためのコードだっけ?」って思うことは何度もあります。
でも、ふと思いました。
「綺麗なコードってどんなコード?」
自分がコーディングをするたびこれはどこに記述するのがベストなのだろうと考えようとはするのですが、最適解がわからず現場で学んでいくしかないのかなと感じていました。そんなとき、下記の記事を見つけました。
Railsのデザインパターンとは
Railsのデザインパターン。これはどんなことを指しているのでしょうか。
Railsにおけるデザインパターンとは、モデルやコントローラ、ビューに頻出する実装パターンを、オブジェクト設計の原則にもとづいて抽象化したパターンのことです。
ここで出てくるキーワードとして「オブジェクト設計の原則」というものがあります。この言葉の意味はざっくりまとめると下記のようになります。
1つのクラスに1つの役割
https://www.amazon.co.jp/オブジェクト指向設計実践ガイド-Rubyでわかる-進化しつづける柔軟なアプリケーションの育て方-Sandi-Metz/dp/477418361X
つまり、Railsのデザインパターンとは、1つのクラスに1つの役割という設計を保つことをRailsのコーディングで実践する方法ということになります。
何故このようなデザインパターンがあるのか。
デザインパターンは、アプリケーションが大きくなるにしたがって起こりがちな設計上の問題を防ぐために必要になります。 代表的な問題がFat ModelやFat Controller、Fat Viewです。Railsはデフォルトでモデルやコントローラ、ビューを用意しています。用意されたこのファイル群だけにコードを記述していくと、オブジェクト指向設計の原則を守るのは難しいです。つまりコードが肥大化してしまいます。 こうなると拡張性や再利用性がなく、またテストも書きづらくなります。これを防ぐために、オブジェクト指向設計の原則に基づいてクラスを分割していく必要があります。
https://applis.io/posts/rails-design-patterns#railsにおけるデザインパターンとは
自分の場合、Fat Controllerが目立ちます。そのため、コードを拡張する時に何度も同じコードを書いている時がたびたびあって、その度に拡張子づらいと思うのです。Railsのデザインパターンとはざっくりまとめると
「Railsで綺麗なコードを書く頻出の方法」
ということができるでしょう。
今回からいくつかの記事にまたがってRailsの全てのデザインパターンをまとめていきたいと思います。デザインパターンは全部で9つあるそうですが、1つずつ紹介していきます
Decoratorオブジェクト
Decoratorオブジェクトの責務
モデルに対するビューのロジックをカプセル化する
Railsではビュー側で表示する見た目・内容を変更したい場合があります。例えば、以下のように日時を表示するviewファイルがあるとします。start_datetime
はdate型のカラムです。
<p> <%= event.start_datetime %><br><br> </p> 表示:2021-09-09 15:00:00 UTC
お分かりのように非常に表示が見づらい。これを日本標準時でYYYY/MM/DD hh:mmの形式で表示する場合、以下のようになります。
<p> <%= event.start_datetime.strftime('%Y/%m/%d %H:%M') %><br><br> </p> 表示:2021/09/09 15:00
しかし、ビュー側にロジックを書くのはよろしくないとされているため、これをモデルに記述します。
# event.rb class Event < ApplicationRecord def formatted_time start_datetime.strftime('%Y/%m/%d %H:%M') end end
<p> <%= event.formatted_time %><br><br> </p> 表示:2021/09/09 15:00
ここでの問題は、viewに表示したいメソッドを追加したけれど、モデルに書くと肥大化してしまうことです。モデルでのメソッドはビューだけでなくコントローラーで使われるメソッドも多く含まれる場合があります。全てのロジックをモデルに移すとFat Modelというモデルにたくさんのコードが書かれてしまう問題が出てきてしまいます。オブジェクト設計の原則である変更しやすいコードから外れてしまいます。
そこで登場するのがDecoratorオブジェクトです。ビューで表示するロジックはモデルではなくDecoratorオブジェクトのファイルに記述する(ロジックのカプセル化)というデザインパターンです。
Decoratorオブジェクトをサポートするgemとして2つのgemがあるそうです。
見たところどちらもメンテナンスは最近されています。しかし、draperの方がスター数が5000に対して、active_decoratorは950程度とかなり差があるため、今回はdraperを使うこととします。
draper
導入
① インストール
Gemfileに記述してインストール
# Gemfile gem 'draper'
$ bundle install
② コマンドのアクティベーション
rails generate draper:install
というコマンドを打つことで次に使用するコマンドを使えるようにします。app/decorators/application_decorator.rb
というファイルが作成されます。
$ bundle exec rails generate draper:install Running via Spring preloader in process 50409 create app/decorators/application_decorator.rb
作成されたファイルは以下のようになります。推測できる通り、ApplicationControllerのように他のdecoratorファイルと共通する処理を書くためのファイルです。今回は使用しません。
# app/decorators/application_decorator.rb class ApplicationDecorator < Draper::Decorator # Define methods for all decorated objects. # Helpers are accessed through `helpers` (aka `h`). For example: # # def percent_amount # h.number_to_percentage object.amount, precision: 2 # end end
③ モデル用のdecorator.rbを生成します。
②で下記コマンドが使えるようになりました。
$ rails generate decorator モデル名
今回はeventモデルを装飾したいため、eventモデルを指定してdecoratorファイルを生成します。
$ bundle exec rails generate decorator event Running via Spring preloader in process 56951 create app/decorators/event_decorator.rb
生成されたファイルは下記になります。
# app/decorators/event_decorator.rb class EventDecorator < Draper::Decorator delegate_all # Define presentation-specific methods here. Helpers are accessed through # `helpers` (aka `h`). You can override attributes, for example: # # def created_at # helpers.content_tag :span, class: 'time' do # object.created_at.strftime("%a %m/%d/%y") # end # end end
ここでdelegate_all
という記述がありますが、これは元のモデルファイルに書かれているロジックがdecoratorファイルでも使用できるようにしています。例えば、先ほど記述したformatted_time
メソッドがモデルファイルevent.rbに記述されていれば、event_decorator.rb
でもformatted_time
メソッドが利用できるようになります。ここで新しく同様のメソッドを書く必要がなくなるので、DRYに則っていますね。
④ decoratorファイルにロジックを記述
# app/decorators/event_decorator.rb class EventDecorator < Draper::Decorator delegate_all def formatted_time object.start_datetime.strftime('%Y/%m/%d %H:%M') end end
ここでいうobject
はモデル自身です。デコレートしているモデルを参照するメソッドと理解しておきます。
ビュー側ではdecorate
と明示して以下のように記載します。
<p> <%= event.decorate.formatted_time %><br><br> </p> 表示:2021/09/09 15:00
helperとの違い
ここでhelperメソッドというこれまたビューを装飾するためのファイルがあると思います。これを使えばロジックをビューやモデルに書くことなく、helperに書くことで同様の実装が可能になると思います。
しかし、helperとdecoratorの用途は差別化されています。
helperはモデルから独立し直接関係していない描画ロジックを実装するのに用います。それに対してDecoratorは特定のモデルにがっつり関連した描画ロジックを実装するのに用いるというものです。
今回の場合、eventモデルのstart_datetimeというカラムをガッツリ参照しています。そのため、decoratorを使用することが正しそうです。
反対に、helperには何を書くのかというと、eventに関する関連ページでeventモデルを参照しない描画ロジックを記述すると思います。例えば、event一覧ページがあるとして、そのページに「本日(YYYY/MM/DD)のイベント」と表示させる時に使えます。
# app/helpers/events_helper.rb module EventsHelper def event_at_date Date.today.to_time.strftime('%Y/%m/%d') end end
参考記事
オブジェクト指向設計原則とは
decoratorを導入して、viewの記述をすっきりさせ、modelの肥大化を回避する【Day 3/30 2nd】
Decoratorの役割とDraperについて