Subscribed unsubscribe Subscribe Subscribe

Rails - ActiveDecoratorのコード読んでみたヘ(^o^)ノ

いつもお世話になります。ActiveDecoratorさま。

@izumin5210さんの「ActiveDecorator読んでみたら超勉強になった」を読んで、そういやコードあんま見てなかったので読んでみた

なるほど編

コードよむ編

# lib/active_decorator/monkey/abstract_controller/rendering.rb
module AbstractController
  module Rendering
    # to_aメソッドのエイリアス↓
    def view_assigns_with_decorator 
      # 既存のto_aメソッドをコール
      hash = view_assigns_without_decorator

      # メソッドを拡張
      hash.values.each do |v|
        ActiveDecorator::Decorator.instance.decorate v
      end
      hash
    end
    
    # :view_assigns => :view_assigns_with_decorator
    # :view_assigns_without_decorator => :view_assigns
    alias_method_chain :view_assigns, :decorator
  end
end

view_assignsは、Controllerで取得したインスタンスなんかが格納されたHashを返しますよー

こんな感じ↓

f:id:murajun1978:20150114225602p:plain

postsはActiveRecord::Relationです(Post.allしたやつ)

testはインスタンス変数に文字列をいれてみた

このHashをActiveDecorator::Decorator

# lib/active_decorator/decorator.rb
module ActiveDecorator
  class Decorator
    include Singleton

    def initialize
      @@decorators = {}
    end

    def decorate(obj)
      [...]
    end
    [...]
  end
end

singletonをMix-inしてるので、ActiveDecorator::Decorator.instance.decorateで呼び出してますよ

decorateメソッド
if obj.is_a?(Array)
  # *2でArrayが引数となるのでここ
  # Arrayの要素(ModelClass)を引数として自身を呼び出してる *3
  obj.each do |r|
    decorate r
  end
elsif defined?(ActiveRecord) && obj.is_a?(ActiveRecord::Relation) && !obj.respond_to?(:to_a_with_decorator)
  # ActiveRecord::Relationならクラスを再オープンしてメソッド定義してる *1
  class << obj
    def to_a_with_decorator
      to_a_without_decorator.tap do |arr|
        # Arrayを引数として自身を呼び出してる *2
        ActiveDecorator::Decorator.instance.decorate arr
      end
    end
    alias_method_chain :to_a, :decorator
  end
else
  # *3で引数がmodelクラスとなるのでここ(ActiveRecord::Relation、Array以外もここ)
  # decoratorのクラス取得してる
  d = decorator_for obj.class
  return obj unless d
  # PostDecoratorモジュールに属してなければextendしてる
  obj.extend d unless obj.is_a? d  
end
decorator_forメソッド
private
def decorator_for(model_class)
  return @@decorators[model_class] if @@decorators.has_key? model_class

  decorator_name = "#{model_class.name}Decorator"
  d = decorator_name.constantize
  unless Class === d
    d.send :include, ActiveDecorator::Helpers
    @@decorators[model_class] = d
  else
    @@decorators[model_class] = nil
  end
rescue NameError
  @@decorators[model_class] = nil
end

ViewContextをごっそり

# lib/active_decorator/view_context.rb
module Filter
  extend ActiveSupport::Concern

  included do
    before_filter do |controller|
      ActiveDecorator::ViewContext.current = controller.view_context
    end
  end
end

helperの拡張

# lib/active_decorator/helpers.rb
module Helpers
  def method_missing(method, *args, &block)
    super
  #TODO need to make sure who raised the error?
  rescue NoMethodError, NameError => original_error
    begin
      # helperでmethod_missingならActiveDecorator::ViewContextから(なければoriginal_errorの例外発生
      ActiveDecorator::ViewContext.current.send method, *args, &block
    rescue NoMethodError, NameError
      raise original_error
    end
  end
end

ViewContextをごっそりとってるから、flashとかもdecoratorで使えるよー

ActiveDecorator::ViewContext.current.flash 
=> #<ActionDispatch::Flash::FlashHash:0x007fda40e27d78 @discard=#<Set: {}>, @flashes={"error"=>"test error"}, @now=nil>
ActiveDecorator::ViewContext.current.flash[:error]
=> "test error"

# app/decorators/post_decorator.rb
module PostDecorator
  def error_flash
    flash[:error]
  end
end

ActiveDecoratorだと読みやすくて勉強にも良いかと思います♪

Happy Hacking٩( ‘ω’ )و

d(゚Д゚ )☆スペシャルサンクス☆( ゚Д゚)b