lib/draper/decorator.rb in draper-1.0.0.beta3 vs lib/draper/decorator.rb in draper-1.0.0.beta4
- old
+ new
@@ -3,44 +3,47 @@
module Draper
class Decorator
include Draper::ViewHelpers
include ActiveModel::Serialization if defined?(ActiveModel::Serialization)
- attr_accessor :source, :options
+ attr_accessor :source, :context
alias_method :model, :source
alias_method :to_source, :source
# Initialize a new decorator instance by passing in
# an instance of the source class. Pass in an optional
- # context inside the options hash is stored for later use.
+ # :context inside the options hash which is available
+ # for later use.
#
# A decorator cannot be applied to other instances of the
# same decorator and will instead result in a decorator
# with the same target as the original.
# You can, however, apply several decorators in a chain but
# you will get a warning if the same decorator appears at
# multiple places in the chain.
#
# @param [Object] source object to decorate
# @param [Hash] options (optional)
+ # @option options [Hash] :context context available to the decorator
def initialize(source, options = {})
+ options.assert_valid_keys(:context)
source.to_a if source.respond_to?(:to_a) # forces evaluation of a lazy query from AR
@source = source
- @options = options
- handle_multiple_decoration if source.is_a?(Draper::Decorator)
+ @context = options.fetch(:context, {})
+ handle_multiple_decoration(options) if source.is_a?(Draper::Decorator)
end
class << self
alias_method :decorate, :new
end
# Specify the class that this class decorates.
#
# @param [String, Symbol, Class] Class or name of class to decorate.
def self.decorates(klass)
- @source_class = klass.to_s.classify.constantize
+ @source_class = klass.to_s.camelize.constantize
end
# @return [Class] The source class corresponding to this
# decorator class
def self.source_class
@@ -70,21 +73,28 @@
# the assocation to be decorated when it is retrieved.
#
# @param [Symbol] association name of association to decorate, like `:products`
# @option options [Class] :with the decorator to apply to the association
# @option options [Symbol] :scope a scope to apply when fetching the association
+ # @option options [Hash, #call] :context context available to decorated
+ # objects in collection. Passing a `lambda` or similar will result in that
+ # block being called when the association is evaluated. The block will be
+ # passed the base decorator's `context` Hash and should return the desired
+ # context Hash for the decorated items.
def self.decorates_association(association, options = {})
+ options.assert_valid_keys(:with, :scope, :context)
define_method(association) do
- decorated_associations[association] ||= Draper::DecoratedAssociation.new(source, association, options)
+ decorated_associations[association] ||= Draper::DecoratedAssociation.new(self, association, options)
decorated_associations[association].call
end
end
# A convenience method for decorating multiple associations. Calls
# decorates_association on each of the given symbols.
#
- # @param [Symbols*] associations name of associations to decorate
+ # @param [Symbols*] associations names of associations to decorate
+ # @param [Hash] options passed to `decorate_association`
def self.decorates_associations(*associations)
options = associations.extract_options!
associations.each do |association|
decorates_association(association, options)
end
@@ -126,11 +136,13 @@
# @param [Object] source collection to decorate
# @param [Hash] options passed to each item's decorator (except
# for the keys listed below)
# @option options [Class,Symbol] :with (self) the class used to decorate
# items, or `:infer` to call each item's `decorate` method instead
+ # @option options [Hash] :context context available to decorated items
def self.decorate_collection(source, options = {})
+ options.assert_valid_keys(:with, :context)
Draper::CollectionDecorator.new(source, options.reverse_merge(with: self))
end
# Get the chain of decorators applied to the object.
#
@@ -242,12 +254,12 @@
def allow?(method)
self.class.security.allow?(method)
end
- def handle_multiple_decoration
+ def handle_multiple_decoration(options)
if source.instance_of?(self.class)
- self.options = source.options if options.empty?
+ self.context = source.context unless options.has_key?(:context)
self.source = source.source
elsif source.decorated_with?(self.class)
warn "Reapplying #{self.class} decorator to target that is already decorated with it. Call stack:\n#{caller(1).join("\n")}"
end
end