2018-02-10

draperを使ってみた

draper というデコレーター(またはプレゼンター)の機能を持つGemを使ってみた記録。

draperとは

  • draper is ...
    • ViewModel for Rails
    • Railsアプリケーションにオブジェクト指向なプレゼンテーション層を追加する
    • Modelにメンテナンス性の高いプレゼンテーションロジックを包み込む

なぜdraperを使うのか

  • Viewにまつわるロジックの置き場としてHelperも存在する。しかし、Helperはグローバルスコープを持っているため全てのControllerとViewで利用できてしまう
  • Helperを使った場合、Modelに応じた微妙な差異を吸収するために新しいメソッドが追加される
    • 例:Articleモデルは日時をYYYY-MM-DD HH:mm:ssで表現したいが、BookモデルはYYYY-MM-DDで表現したい
  • Helperはオブジェクト指向に書きづらい

→ draperはこのような問題を解決する

使ってみる

セットアップ

gem 'draper'をGemfileに追記してインストールしたら、ジェネレートコマンドを使って一式生成する。
Userモデルがあるとして、それに対応するdecoratorを生成する。

>> bin/rails g decorator User
      create  app/decorators/user_decorator.rb
      invoke  test_unit
      create    test/decorators/user_decorator_test.rb

app以下にdecoratorsディレクトリが作成され、その下にuser_decorator.rbが追加された。

デコレートメソッドを追加する

早速Userモデルをデコレートしていく。
ここでは、Userが自己紹介をするためのデコレートメソッドを追加した。

# app/decorators/user_decorator.rb
def greeting
  "Hi! My name is #{user.name}. I am #{user.age} years old. Thank you."
end

decoratorクラス内で、対応するモデルにアクセスするには object か 対応するモデル名(Userモデルの場合は user)を呼び出す。

Controllerでモデルをデコレートする

Modelをデコレートする準備は整ったので、次はControllerで取得したModelをデコレートした上でViewに受け渡す。
次のように、Modelに対して decorate メソッドを呼び出すだけでOK。

# app/controllers/users_controller.rb
def show
  @user = User.find_by(id: params[:id]).decorate
end

Viewでデコレートメソッドを呼び出す

Viewではデコレートされていることを意識する必要はなく、普通にメソッドを呼び出すだけでいい。

<!-- app/views/users/show.html.erb -->
<h1><%= @user.greeting %></h1>

画面

2018-02-10-01.png
2018-02-10-01.png

これでdraperを使った一連の実装を体感できた。

アソシエーションに対応する

関連するモデルも含めてデコレートしたい場合は、次のようにデコレータクラスで decorates_association メソッドを呼び出す。

# app/decorators/user_decorator.rb
class UserDecorator < Draper::Decorator
  # ここでは、UserはN個のDeckを持つ関連が設定されているとする
  decorates_association :decks
end

これで、deck側もデコレートされたオブジェクトとして扱うことができる

その他のメモ

  • decoratorクラス内で、helperにアクセスするには hを呼び出す
    • 例:h.content_tag(:strong, user.name)
  • コレクションをdecorateするいくつかの方法があるようだ
    • UserDecorator.decorate_collection(User.all)
    • User.all.decorate
    • これは具体的に何が違うのかまで調べていない
  • ApplicationDecoratorなどとして、Decorator共通の処理を括りだすことができる。この場合、各デコレータクラスはDraper::DecoratorではなくApplicationDecoratorを継承するようにする
    • class ApplicationDecorator < Draper::Decorator
    • class UserDecorator < ApplicationDecorator
  • Decoratorクラスはデフォルトでdeltega_allが呼び出され、全てのメソッド呼び出しを委譲しているが、delgateメソッドで個別に指定することも可能
  • どのデコレータを使うかの指定も可能。デフォルトではファイル名から判定されている。