Rails
Draper

Decoratorの役割とDraperについて

本記事の内容

  • Decoratorとはなにか
  • Draperとはなにか
  • Decoratorの利便性
  • なぜモデルではだめなのか
  • なぜヘルパーではだめなのか
  • なぜモジュールではだめなのか
  • まとめ

Decoratorとはなにか

ソフトウェアのデザインパターンの一つです。
既存のオブジェクトを新しいDecoratorオブジェクトでラップすることで既存の関数やクラスの中身を直接触ることなく、その外側から機能を追加したり書き換えたりする。また、既存のクラスを拡張する際にクラスの継承の代替手段として用いられます。
Decoratorというデザインパターンを導入することで、ビューファイルにロジックを記述しないといったことができます。

Draperとはなにか

DraperはRailsのプレゼンテーション層の役割を担うgemです。
プレゼンテーション層とはviewとモデルの中間に位置し、モデルやビューに実装されやすい表示ロジック/フォーマットを記述する役割を担います。

Draperの導入はこちらを参考にしてください
https://github.com/drapergem/draper

Decoratorの利便性

具体的にDecoratorのデザインパターンを導入することでどういった利便性があるのでしょうか。

# Draper導入前
<dt>Twitter:</dt>
<dd>
<% if @user.twitter_name.present? %>
  <%= @user.twitter_name %>
<% else %>
  None given
<% end %>
</dd>


# Draper導入後
<dt>Twitter:</dt>
<dd><%= @user.twitter %></dd>
</dd>

上記のようにプレゼンテーション層にtweetというメソッドを用意することで、viewのロジックを減らすことができます。

どのようにしてプレゼンテーション層を作成していくのでしょうか。
ここではuserモデルがすでに存在することを前提に進めて行きます。
例なので動かない可能性があります、役割だけご確認ください

まずuserのデコレータクラスを作成します。このクラスにモデルの拡張したいメソッドを記述していきます。

rails generate decorator User

今回用意するtweetメソッドでは、もしuserにtweet_nameが存在する場合はtweet_nameを表示し、存在しない場合は"None given"というものを返すものです。

class UserDecorator < ApplicationDecorator
  delegate_all

  def tweet
    if working_hours.present?
      twitter_name
    else
      return "None given"
    end
  end
end

delegate_allとは、UserモデルのメソッドをUserDecoratorクラスでも使用できるようにするための記述です。これがあることによってuser.nameなどuserモデルが所有するインスタンスメソッドがUserDecoratorクラスのオブジェクトにも使用することが可能となります。

これが最初に記述した「既存のオブジェクトを新しいDecoratorオブジェクトでラップすることで既存の関数やクラスの中身を直接触ることなく、その外側から機能を追加したり書き換えたりする。」という部分になります。

なぜモデルではだめなのか

何でもモデルに記載しておくと、すぐにモデルは肥大化してしまいます。
モデルにはDBにアクセスするような処理のみを記載することで肥大化を防ぎましょう。特に今回のようなビューロジックはモデルには書かないほうがいいと言えるでしょう。

なぜhelperではだめなのか

helperもDecoratorもビューを書きやすくするための仕組みという点では共通しています。
しかしこの2つの役割の違いは
helperはモデルから独立し直接関係していない描画ロジックを実装するのに用います。
それに対して
Decoratorは特定のモデルにがっつり関連した描画ロジックを実装するのに用いる
というものです。

やろうと思えばDecoratorで行う作業をhelperでも実現することは可能なのですが、それではそれぞれの責務から外れてしまいます。

例えば、数値を3桁ずつに分けてカンマを付けるような「数値」にしか関連していないロジックはヘルパーに実装します。1000を1,000にするような関数です。

またhelperのメソッドはよくコントローラ単位、ビュー単位のローカル関数であると誤解されるのですが、グローバル空間で定義されるためアプリケーション内でのメソッド名の重複に気をつける必要があります。特にチーム開発を行う場合はヘルパーを書いているときはかぶらないように特に注意する必要があります。

なぜモジュールではだめなのか

モジュールで拡張用のメソッドを定義してモデルのクラスにインクルードすれば同じことを再現できそうです。しかし結局これもモデルに記載しているのとあまり変わり有りません。
もし1人で開発している場合であれば、モジュールがviewのロジックで...、モデルにはDBのアクセスして...、というルールのもとで実装できるかもしれませんが、共同開発の場合にはそのルールを共有していない場合、モデルにビューロジックが溢れ出すこととなりやすいからです。

まとめ

Decoratorを利用して、モデル、ヘルパーそれぞれの役割を明確にしてあげることで、よりわかりやすいコードをかくことができます。Decoratorをつかうことでviewが驚く程すっきりしました、是非使ってみてください〜。

参考文献

http://ruby-rails.hatenadiary.com/entry/20150415/1429031791