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