diff --git a/lib/dry/container/item.rb b/lib/dry/container/item.rb index dc01875..e215405 100644 --- a/lib/dry/container/item.rb +++ b/lib/dry/container/item.rb @@ -23,6 +23,27 @@ def initialize(item, options = {}) def call raise NotImplementedError end + + # @private + def value? + !callable? + end + + # @private + def callable? + options[:call] + end + + # Build a new item with transformation applied + # + # @private + def map(func) + if callable? + self.class.new(-> { func.(item.call) }, options) + else + self.class.new(func.(item), options) + end + end end end end diff --git a/lib/dry/container/item/callable.rb b/lib/dry/container/item/callable.rb index c0d5e1c..eafdaa5 100644 --- a/lib/dry/container/item/callable.rb +++ b/lib/dry/container/item/callable.rb @@ -14,13 +14,6 @@ class Callable < Item def call callable? ? item.call : item end - - private - - # @private - def callable? - options[:call] - end end end end diff --git a/lib/dry/container/item/memoizable.rb b/lib/dry/container/item/memoizable.rb index ce1f752..d8ebb3a 100644 --- a/lib/dry/container/item/memoizable.rb +++ b/lib/dry/container/item/memoizable.rb @@ -21,7 +21,7 @@ class Memoizable < Item # @return [Dry::Container::Item::Base] def initialize(item, options = {}) super - raise_not_supported_error unless item.is_a?(::Proc) + raise_not_supported_error unless callable? @memoize_mutex = ::Mutex.new end diff --git a/lib/dry/container/mixin.rb b/lib/dry/container/mixin.rb index 3efc9aa..a170aa1 100644 --- a/lib/dry/container/mixin.rb +++ b/lib/dry/container/mixin.rb @@ -6,6 +6,9 @@ class Container PREFIX_NAMESPACE = lambda do |namespace, key, config| [namespace, key].join(config.namespace_separator) end + + EMPTY_HASH = {}.freeze + # Mixin to expose Inversion of Control (IoC) container behaviour # # @example @@ -90,7 +93,7 @@ def config # @return [Dry::Container::Mixin] self # # @api public - def register(key, contents = nil, options = {}, &block) + def register(key, contents = nil, options = EMPTY_HASH, &block) if block_given? item = block options = contents if contents.is_a?(::Hash) @@ -208,23 +211,22 @@ def each(&block) # @return [Dry::Container::Mixin] self # # @api public - def decorate(key, with:) - original = _container.delete(key.to_s) do + def decorate(key, with: nil, &block) + key = key.to_s + original = _container.delete(key) do raise Error, "Nothing registered with the key #{key.inspect}" end - decorator = with - memoize = original.is_a?(Item::Memoizable) - - if decorator.is_a?(Class) - decorated = -> { decorator.new(original.call) } - elsif decorator.respond_to?(:call) - decorated = -> { decorator.call(original.call) } + if with.is_a?(Class) + decorator = with.method(:new) + elsif block.nil? && !with.respond_to?(:call) + raise Error, "Decorator needs to be a Class, block, or respond to the `call` method" else - raise Error, "Decorator needs to be a Class or responds to the `call` method" + decorator = with || block end - register(key, memoize: memoize, &decorated) + _container[key] = original.map(decorator) + self end # Evaluate block and register items in namespace diff --git a/spec/support/shared_examples/container.rb b/spec/support/shared_examples/container.rb index cbde23a..8e7b0ee 100644 --- a/spec/support/shared_examples/container.rb +++ b/spec/support/shared_examples/container.rb @@ -426,14 +426,34 @@ end context 'for not callable item' do - before do - container.register(key, call: false) { "value" } - container.decorate(key, with: SimpleDelegator) + describe 'wrapping' do + before do + container.register(key, call: false) { "value" } + container.decorate(key, with: SimpleDelegator) + end + + it 'expected to be an instance of SimpleDelegator' do + expect(container.resolve(key)).to be_instance_of(SimpleDelegator) + expect(container.resolve(key).__getobj__.call).to eql("value") + end end - it 'expected to be an instance of SimpleDelegator' do - expect(container.resolve(key)).to be_instance_of(SimpleDelegator) - expect(container.resolve(key).__getobj__.call).to eql("value") + describe 'memoization' do + before do + @called = 0 + container.register(key, 'value') + + container.decorate(key) do |value| + @called += 1 + "<#{value}>" + end + end + + it 'decorates static value only once' do + expect(container.resolve(key)).to eql('') + expect(container.resolve(key)).to eql('') + expect(@called).to be(1) + end end end