RailsのデコレーターGem、draperとactive_decoratorを比較してみた

# はじめに
- Railsにおけるデコレーターとはそもそも何か。

> Imagine your application has an Article model. With Draper, you'd create a corresponding ArticleDecorator. The decorator wraps the model, and deals only with presentational concerns. In the controller, you decorate the article before handing it off to the view
訳）あなたのアプリにArticleモデルがあったとします。Draperを使うと、そのモデルに対応するArticleDecoratorを作成することになります。デコレーターはモデルをラップして、表示に関する処理のみを管理します。なおコントローラー内で使うときは、ビューに表示する前にモデルをデコレーションする必要があります。

- つまり**表示に関する処理を切り出して、モデルの肥大化を防ぐ為にデコレーターを作る**ということです。
- わかりやすい例えで言うと、`User`モデルの`first_name`と`last_name`をまとめた`full_name`というメソッドはデコレーター案件です。

```rb
# 表示に関する処理
def full_name
  "#{last_name} #{first_name}"
end
```

- そのデコレーターを実現する方法として、`draper`（ドレイパー？ドラパー？某Railsコミッターさんはドレイパーと仰っていたが‥‥）と`active_decorator`とがあります。
- 今回はこの二つを深く比較していこうと思います。

# Draper
- https://github.com/drapergem/draper
- [draperコードリーディング](https://blog.freedom-man.com/draper-codereading/)
- まずは私が長らくお世話になっている`draper`から。

## generator
- `rails g decorator user`と打ったとき、[lib/generators/rails/decorator_generator.rb](https://github.com/drapergem/draper/blob/master/lib/generators/rails/decorator_generator.rb)にある通り、テンプレートをクラス名に応じたファイル名で追加する。
- そのファイル内に登場する`delegate_all`は以下の道筋を通って、Decoratorクラス内にメソッドがなければ、元のモデルにフォールバックするようにしている。

```rb
# lib/draper/decorator.rb
def self.delegate_all
  include Draper::AutomaticDelegation
end

# lib/draper/automatic_delegation.rb
def method_missing(method, *args, &block)
  return super unless delegatable?(method)

  object.send(method, *args, &block)
end

private def delegatable?(method)
  object.respond_to?(method)
end
```

## decorate
- `Railtie`を使って、`ActiveModel`に`include Draper::Decoratable`を実行している。
- 前提として**デコレーターはDraper::Decoratorの継承であること**を覚えておく
- ActiveRecordから`decorate`を呼び出した場合

```rb
# lib/draper/decoratable.rb
def decorate(options = {})
  # このselfはUserで、decorator_classはUserDecorator
  decorator_class.decorate(self, options)
end

def decorator_class
  self.class.decorator_class
end

class << self
  def decorator_class(called_on = self)
    # 要約すると、UserモデルならUserDecoratorモデルを返す
  end
end

# lib/draper/decorator.rb
class << self
  def initialize(object, options = {})
    # 元のActiveRecordをobjectとして所持
    @object = object
  end

  alias :decorate :new
end
```

# ActiveDecorator
- draperと違って、特にdecorateモデルをつけなくてもデコレーターメソッドを使える。
- というのも、**初期化時に`ActiveModel`や`ActionController`に対してdecorateメソッドを実行しきっているから**。

## decorate
- `decorate`メソッドを実行すると、`draper`とは違って単純にデコレータークラスを`extend`してくれる。

```rb
class Decorator
  include Singleton

  def initialize
    @@decorators = {}
  end

  # ArrayかActiveRecord::Relation、ActiveRecord::Baseだけdecorateする。
  # それ以外はそのまま返す。
  # decorateされていた場合にもそのまま返す
  # シングルトンクラスのメソッドなので、こうやって呼び出す。
  # `ActiveDecorator::Decorator.instance.decorate(obj)`.
  def decorate(obj)
    # Jbuilderとnilを弾く
    return if defined?(Jbuilder) && (Jbuilder === obj)
    return if obj.nil?

    if obj.is_a?(Array)
      obj.each do |r|
        decorate r
      end
    elsif defined?(ActiveRecord) && obj.is_a?(ActiveRecord::Relation)
      # recordsメソッドを呼んだ時に、自動的にdecorateされた物を返すようにする
      if obj.respond_to?(:records)
        # Rails 5.0
        obj.extend ActiveDecorator::RelationDecorator unless obj.is_a? ActiveDecorator::RelationDecorator
      # Rails 5.x以前はrecordsメソッドがなかったんか‥‥
      else
        # Rails 3.x and 4.x
        obj.extend ActiveDecorator::RelationDecoratorLegacy unless obj.is_a? ActiveDecorator::RelationDecoratorLegacy
      end
    else
      # ActiveDecorator::Decoratedがextendされているか確認
      # ActiveDecorator::Decorated自体は空のモジュールであり、そのクラスがデコレートされたかどうかのフラグの役割を果たしている。
      if defined?(ActiveRecord) && obj.is_a?(ActiveRecord::Base) && !obj.is_a?(ActiveDecorator::Decorated)
        obj.extend ActiveDecorator::Decorated
      end

      # 最後に、objectがまだdecorateされてなければ（ModelnameDecorateがextendされてなければ）extendする
      d = decorator_for obj.class
      return obj unless d
      obj.extend d unless obj.is_a? d
    end

    obj
  end

  private
  # Returns a decorator module for the given class.
  # Returns `nil` if no decorator module was found.
  def decorator_for(model_class)
    return @@decorators[model_class] if @@decorators.key? model_class

    decorator_name = "#{model_class.name}#{ActiveDecorator.config.decorator_suffix}"
    d = decorator_name.constantize
    unless Class === d
      d.send :include, ActiveDecorator::Helpers
      @@decorators[model_class] = d
    else
      # Cache nil results
      @@decorators[model_class] = nil
    end
  rescue NameError
    if model_class.respond_to?(:base_class) && (model_class.base_class != model_class)
      @@decorators[model_class] = decorator_for model_class.base_class
    else
      # Cache nil results
      @@decorators[model_class] = nil
    end
  end
end

# For AR 3 and 4
module RelationDecoratorLegacy
  def to_a
    super.tap do |arr|
      ActiveDecorator::Decorator.instance.decorate arr
    end
  end
end

# For AR 5+
module RelationDecorator
  def records
    super.tap do |arr|
      ActiveDecorator::Decorator.instance.decorate arr
    end
  end
end
```

# 終わりに
- 解体して見ると、設計思想って色々あるんだなぁと思った