drapergem/draper

View on GitHub
lib/draper/collection_decorator.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
module Draper
  class CollectionDecorator
    include Enumerable
    include Draper::ViewHelpers
    include Draper::QueryMethods
    extend Draper::Delegation

    # @return the collection being decorated.
    attr_reader :object

    # @return [Class] the decorator class used to decorate each item, as set by
    #   {#initialize}.
    attr_reader :decorator_class

    # @return [Hash] extra data to be used in user-defined methods, and passed
    #   to each item's decorator.
    attr_accessor :context

    array_methods = Array.instance_methods - Object.instance_methods
    delegate :==, :as_json, *array_methods, to: :decorated_collection

    # @param [Enumerable] object
    #   collection to decorate.
    # @option options [Class, nil] :with (nil)
    #   the decorator class used to decorate each item. When `nil`, each item's
    #   {Decoratable#decorate decorate} method will be used.
    # @option options [Hash] :context ({})
    #   extra data to be stored in the collection decorator and used in
    #   user-defined methods, and passed to each item's decorator.
    def initialize(object, options = {})
      options.assert_valid_keys(:with, :context)
      @object = object
      @decorator_class = options[:with]
      @context = options.fetch(:context, {})
    end

    class << self
      alias :decorate :new
    end

    # @return [Array] the decorated items.
    def decorated_collection
      @decorated_collection ||= object.map{|item| decorate_item(item)}
    end

    delegate :find, to: :decorated_collection

    def to_s
      "#<#{self.class.name} of #{decorator_class || "inferred decorators"} for #{object.inspect}>"
    end

    def context=(value)
      @context = value
      each {|item| item.context = value } if @decorated_collection
    end

    # @return [true]
    def decorated?
      true
    end

    alias :decorated_with? :instance_of?

    def kind_of?(klass)
      decorated_collection.kind_of?(klass) || super
    end

    alias_method :is_a?, :kind_of?

    def replace(other)
      decorated_collection.replace(other)
      self
    end

    protected

    # Decorates the given item.
    def decorate_item(item)
      item_decorator.call(item, context: context)
    end

    private

    def item_decorator
      if decorator_class
        decorator_class.method(:decorate)
      else
        ->(item, options) { item.decorate(options) }
      end
    end
  end
end