Draper、混沌としてきたhelperの代替策のひとつ


Rails 4.0.0
ruby 2.0.0p247

## Draperとは？

具体例を書いた方が分かりやすいと思うのでコードで。

### Draperなし（helper頼み）

```ruby:controllers/products_controller.rb
class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
  end
end
```

```ruby:helpers/products_helper.rb
module ProductsHelper
  def product_name_with_hoge(product)
    "#{product.name} hoge"
  end
end
```

```ruby:views/products/show.html.haml
%p=product_name_with_hoge(@product)
```

みたいな感じになってしまうので、ここだけオブジェクト志向の世界から抜け出た感じになってしまいます。

viewのコードにproductって単語が繰り返されているのもまた気持ち悪いです。
でも、`name_with_hoge`みたいなメソッド名にしてしまうと別のhelperと競合してしまう可能性があるので仕方ないですね。

### Draperあり（helperなし）

```ruby:controllers/products_controller.rb
class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id]).decorate
  end
end
```

あらたに`#decorate`を呼び出しています。

```ruby:decorators/products_decorator.rb
class ProductDecorator < Draper::Decorator
  def name_with_hoge
    "#{object.name} hoge"
  end
end
```

```ruby:views/products/show.html.haml
%p=@product.name_with_hoge
```

Viewのコードがスッキリになりましたね。
なにより直感的なコードになったと思います。

helperはちょこっと何かをしたいという時は便利だと思うのですが、Viewがゴチャゴチャしがちな気がします。

## ほかにできる便利なこと

### helperにアクセスできる

```ruby:decorators/product_decorator.rb
class ProductDecorator < Draper::Decorator
  def name_with_icon
    content = h.content_tag(:i, nil, class: 'icon-user')
    content << object.name
  end
end
```

`#h`で既存のhelperにアクセス出来ます。
Railsは便利なhelperがたくさんありますからね。

### 関連モデルを自動的に`#decorate`する

```ruby:decorators/product_decorator.rb
class ProductDecorator < Draper::Decorator
  decorates_association :price
end
```

```ruby:decorators/price_decorator.rb
class PriceDecorator < Draper::Decorator
  def created_at_with_hoge
    "#{object.created_at} hoge"
  end
end
```

```ruby:views/products/show.html.haml
%p=@product.price.created_at_with_hoge
```

のようにいちいち`#decorate`しなくても勝手にdecoratedなmodelがもらえます。

### attributeをフィルターする

Viewからアクセスできるattributeを制限したい場合などもたぶん良いです。
アクセスしたいattributeだけを明示的に`#delegate`します。
（この場合、ふだん`#delegate`に必要な`to:`は推測できるので省略できます）

```ruby:decorators/product_decorator.rb
class ProductDecorator < Draper::Decorator
  delegate :name
end
```

そんなこと気にしない場合は、

```ruby:decorators/product_decorator.rb
class ProductDecorator < Draper::Decorator
  delegate_all
end
```

とすれば、Decoratorに定義されていないメソッドはmodelに渡されます。

### 共通のメソッドはmoduleで書きだしたり

Rails4(37signales)なスタイルを真似て、`decorators/concerns`みたいなディレクトリ作って書き出してもいいかもね。

```ruby:decorators/concerns/timestamps_decoration.rb
module TimestampsDecoration
  def created_at
    object.created_at.strftime('%F %R')
  end
end
```

```ruby:config/application.rb
config.eager_load_paths += Dir[Rails.root.join('app', 'decorators', 'concerns')]
```

```ruby:decorators/product_decorator.rb
class ProductDecorator < Draper::Decorator
  include TimestampsDecoration
end
```

でも、`TimestampDecoration `に対して`concerns`ってフォルダ名は適切なじゃない気もするな。。

## まとめ

modelに属さない便利メソッドはまたhelperとして居続けていいと思うんですよね。

ただ、modelに`name_with_hoge`みたいなメソッドがあると気持ち悪し、helperに書いてもイマイチなので、decorator(presenter)として書き出すのがいいんじゃないかなと思います。

### DraperのほかにもActiveDecorator

ほかに、同様のことが出来るものにamatsudaさんの[ActiveDecorator](https://github.com/amatsuda/active_decorator)があります。

その他は、[Ruby Tool Box - Rails Presenters](https://www.ruby-toolbox.com/categories/rails_presenters)にありますが、ActiveDecoratorかDraperを選ぶ人が多そうです。

好みでどちらを使うか選んだらいいかなと思います。

### あとがき

ほんとうは`concerns`のところだけ書く予定が、Draperの記事がQiitaに見当たらなかったので、まるっと書いてみた次第です。

ほとんど`README.md`の和訳だが…
詳しくは、[drapergem/draper](https://github.com/drapergem/draper)を参照のこと！