draper/lib/draper/factory.rb

92 lines
2.8 KiB
Ruby

module Draper
class Factory
# Creates a decorator factory.
#
# @option options [Decorator, CollectionDecorator] :with (nil)
# decorator class to use. If nil, it is inferred from the object
# passed to {#decorate}.
# @option options [Hash, #call] context
# extra data to be stored in created decorators. If a proc is given, it
# will be called each time {#decorate} is called and its return value
# will be used as the context.
def initialize(options = {})
options.assert_valid_keys(:with, :context)
@decorator_class = options.delete(:with)
@default_options = options
end
# Decorates an object, inferring whether to create a singular or collection
# decorator from the type of object passed.
#
# @param [Object] object
# object to decorate.
# @option options [Hash] context
# extra data to be stored in the decorator. Overrides any context passed
# to the constructor.
# @option options [Object, Array] context_args (nil)
# argument(s) to be passed to the context proc.
# @return [Decorator, CollectionDecorator] the decorated object.
def decorate(object, options = {})
return nil if object.nil?
Worker.new(decorator_class, object).call(options.reverse_merge(default_options))
end
private
attr_reader :decorator_class, :default_options
# @private
class Worker
def initialize(decorator_class, object)
@decorator_class = decorator_class
@object = object
end
def call(options)
update_context options
decorator.call(object, options)
end
def decorator
return decorator_method(decorator_class) if decorator_class
return object_decorator if decoratable?
return decorator_method(Draper::CollectionDecorator) if collection?
raise Draper::UninferrableDecoratorError.new(object.class)
end
private
attr_reader :decorator_class, :object
def object_decorator
if collection?
->(object, options) { object.decorator_class.decorate_collection(object, options.reverse_merge(with: nil))}
else
->(object, options) { object.decorate(options) }
end
end
def decorator_method(klass)
if collection? && klass.respond_to?(:decorate_collection)
klass.method(:decorate_collection)
else
klass.method(:decorate)
end
end
def collection?
object.respond_to?(:first) && !object.is_a?(Struct)
end
def decoratable?
object.respond_to?(:decorate)
end
def update_context(options)
args = options.delete(:context_args)
options[:context] = options[:context].call(*Array.wrap(args)) if options[:context].respond_to?(:call)
end
end
end
end