dry-container 0.6.0 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 35fd3e8023c0220150dade45164cdefbe011633c
4
- data.tar.gz: 4060a17b5831ef9c1fe7aa4440e0c34ef7d8fb4e
2
+ SHA256:
3
+ metadata.gz: ac1a656d67e37d72da3265e2f092e507460cba4615db8f3727ee1acbca41584c
4
+ data.tar.gz: be191bd45d43147d12c858d8683463be5e59a66ef2f473e8c19d7010bda0794c
5
5
  SHA512:
6
- metadata.gz: 9e1f8876a5ea3cdb77b8516e1d85481ed86b58d70c8d41aa77d71c45fbf30bdd1b4c226a60a50a0795fde809934461a8c72ff6225b6a2b4b9bd6e468cb369709
7
- data.tar.gz: bf68f2feb7608a30d851f69c7e370741a0cf2af50e539abe027cad53ce3714b7965b8964266b5e045fabb43fcdcb6f86f95b9398de874b8f7752cf4c56797558
6
+ metadata.gz: c95deccc6ad3f976d31949d1a39e1bc4d6f5797604045aed3f7254d3a1e88ca35c76ee0483ad14bbc6abe0aed8fb13d157cd793c6eb1ff417343a0037a56358f
7
+ data.tar.gz: a6bac7cb3f1819b6bb89a2989de1fe0c3d9a182144a556394cec1f8a1d48bf848589c57ffc7b4daaac106cb0a5673b086f901f2b1deba81c64796a6a1038c6c0
data/.gitignore CHANGED
@@ -7,3 +7,4 @@ tmp/
7
7
  .idea/
8
8
  Gemfile.lock
9
9
  spec/examples.txt
10
+ pkg/
data/.travis.yml CHANGED
@@ -8,22 +8,18 @@ script:
8
8
  after_success:
9
9
  - '[ -d coverage ] && bundle exec codeclimate-test-reporter'
10
10
  rvm:
11
- - 2.0
12
- - 2.1
13
- - 2.2
14
- - 2.3.1
15
- - rbx-3
16
- - jruby-9.1.5.0
17
- - ruby-head
18
- - jruby-head
11
+ - 2.4.6
12
+ - 2.5.5
13
+ - 2.6.3
14
+ - jruby-9.2.7.0
15
+ - truffleruby
16
+ matrix:
17
+ allow_failures:
18
+ - rvm: truffleruby
19
19
  env:
20
20
  global:
21
21
  - JRUBY_OPTS='--dev -J-Xmx1024M'
22
22
  - COVERAGE='true'
23
- matrix:
24
- allow_failures:
25
- - rvm: ruby-head
26
- - rvm: jruby-head
27
23
  notifications:
28
24
  email: false
29
25
  webhooks:
data/CHANGELOG.md CHANGED
@@ -1,10 +1,85 @@
1
- ## Unreleased
1
+ ## v0.7.2 - 2019-07-09
2
+
3
+ ## Added
4
+
5
+ * `.resolve` accepts an optional fallback block, similar to how `Hash#fetch` works ([flash-gordon](https://github.com/flash-gordon))
6
+ ```ruby
7
+ container.resolve('missing_key') { :fallback } # => :fallback
8
+ ```
9
+ * `.decorate` can (again) work with static values. Also, it can take a block instead of `with` ([flash-gordon](https://github.com/flash-gordon))
10
+ ```ruby
11
+ container.register('key', 'value')
12
+ container.decorate('key') { |v| "<'#{v}'>" }
13
+ container.resolve('key') # => "<'value'>"
14
+ ```
15
+
16
+ [Compare v0.7.1...0.7.2](https://github.com/dry-rb/dry-container/compare/v0.7.1...v0.7.2)
17
+
18
+ ## v0.7.1 - 2019-06-07
19
+
20
+ ## Fixed
21
+
22
+ * Added `Mixin#dup` and `Mixin#clone`, now copies don't share underlying containers (flash-gordon)
23
+
24
+ [Compare v0.7.0...0.7.1](https://github.com/dry-rb/dry-container/compare/v0.7.0...v0.7.1)
25
+
26
+ ## v0.7.0 - 2019-02-05
27
+
28
+ ## Changed
29
+
30
+ * [BREAKING] Now only Ruby 2.3 and above is supported ([flash-gordon](https://github.com/flash-gordon))
31
+
32
+ ## Fixed
33
+
34
+ * Symbols are now coerced to strings when resolving stubbed dependencies ([cthulhu666](https://github.com/cthulhu666))
35
+ * Stubbing keys not present in container will raise an error ([flash-gordon](https://github.com/flash-gordon))
36
+
37
+ This means after upgrading you may see errors like this
38
+ ```
39
+ ArgumentError (cannot stub "something" - no such key in container)
40
+ ```
41
+ Be sure you register dependencies before using them. The new behavior will likely save quite a bit of time when debugging ill-configured container setups.
42
+
43
+ ## Added
44
+
45
+ * Namespace DSL resolves keys relative to the current namespace, see the corresponding [changes](https://github.com/dry-rb/dry-container/pull/47) ([yuszuv](https://github.com/yuszuv))
46
+ * Registered objects can be decorated with the following API ([igor-alexandrov](https://github.com/igor-alexandrov))
47
+
48
+ ```ruby
49
+ class CreateUser
50
+ def call(params)
51
+ # ...
52
+ end
53
+ end
54
+ container.register('create_user') { CreateUser.new }
55
+ container.decorate('create_user', with: ShinyLogger.new)
56
+
57
+ # Now subsequent resolutions will return a wrapped object
58
+
59
+ container.resolve('create_user')
60
+ # => #<ShinyLogger @obj=#<CreateUser:0x...>]>
61
+ ```
62
+ * Freezing a container now prevents further registrations ([flash-gordon](https://github.com/flash-gordon))
63
+
64
+ ## Internal
65
+
66
+ * Handling container items was generalized in [#34](https://github.com/dry-rb/dry-container/pull/34) ([GabrielMalakias](https://github.com/GabrielMalakias))
67
+
68
+ [Compare v0.6.0...0.7.0](https://github.com/dry-rb/dry-container/compare/v0.6.0...v0.7.0)
69
+
70
+ ## v0.6.0 - 2016-12-09
2
71
 
3
72
  ## Added
4
73
 
5
74
  * `Dry::Container::Mixin#each` - provides a means of seeing what all is registered in the container ([jeremyf](https://github.com/jeremyf))
6
75
 
7
- ## v0.5.0
76
+ ## Fixed
77
+
78
+ * Including mixin into a class with a custom initializer ([maltoe](https://github.com/maltoe))
79
+
80
+ [Compare v0.5.0...v0.6.0](https://github.com/dry-rb/dry-container/compare/v0.5.0...v0.6.0)
81
+
82
+ ## v0.5.0 - 2016-08-31
8
83
 
9
84
  ## Added
10
85
 
@@ -14,4 +89,4 @@
14
89
 
15
90
  * `required_ruby_version` set to `>= 2.0.0` ([artofhuman](https://github.com/artofhuman))
16
91
 
17
- [Compare v0.4.0...HEAD](https://github.com/dry-rb/dry-container/compare/v0.4.0...v0.5.0)
92
+ [Compare v0.4.0...v0.5.0](https://github.com/dry-rb/dry-container/compare/v0.4.0...v0.5.0)
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,29 @@
1
+ # Issue Guidelines
2
+
3
+ ## Reporting bugs
4
+
5
+ If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated.
6
+
7
+ ## Reporting feature requests
8
+
9
+ Report a feature request **only after discussing it first on [discourse.dry-rb.org](http://discourse.dry-rb.org)** where it was accepted. Please provide a concise description of the feature, don't link to a discussion thread, and instead summarize what was discussed.
10
+
11
+ ## Reporting questions, support requests, ideas, concerns etc.
12
+
13
+ **PLEASE DON'T** - use [discourse.dry-rb.org](http://discourse.dry-rb.org) instead.
14
+
15
+ # Pull Request Guidelines
16
+
17
+ A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc.
18
+
19
+ Other requirements:
20
+
21
+ 1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue.
22
+ 2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style.
23
+ 3) Add API documentation if it's a new feature
24
+ 4) Update API documentation if it changes an existing feature
25
+ 5) Bonus points for sending a PR to [github.com/dry-rb/dry-rb.org](github.com/dry-rb/dry-rb.org) which updates user documentation and guides
26
+
27
+ # Asking for help
28
+
29
+ If these guidelines aren't helpful, and you're stuck, please post a message on [[discourse.dry-rb.org](http://discourse.dry-rb.org).
data/Gemfile CHANGED
@@ -15,4 +15,5 @@ group :tools do
15
15
  gem 'guard-rspec'
16
16
  gem 'guard-rubocop'
17
17
  gem 'listen', '3.0.6'
18
+ gem 'pry-byebug', platform: :mri
18
19
  end
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2014 Ruby Object Mapper
3
+ Copyright (c) 2015-2017 dry-container
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -1,171 +1,23 @@
1
- [gitter]: https://gitter.im/dry-rb/chat
2
1
  [gem]: https://rubygems.org/gems/dry-container
3
2
  [travis]: https://travis-ci.org/dry-rb/dry-container
4
- [code_climate]: https://codeclimate.com/github/dry-rb/dry-container
3
+ [maintainability]: https://codeclimate.com/github/dry-rb/dry-container/maintainability
4
+ [test_coverage]: https://codeclimate.com/github/dry-rb/dry-container/test_coverage
5
5
  [inch]: http://inch-ci.org/github/dry-rb/dry-container
6
+ [chat]: https://dry-rb.zulipchat.com
6
7
 
7
- # dry-container [![Join the Gitter chat](https://badges.gitter.im/Join%20Chat.svg)][gitter]
8
+ # dry-container [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
8
9
 
9
10
  [![Gem Version](https://img.shields.io/gem/v/dry-container.svg)][gem]
10
11
  [![Build Status](https://img.shields.io/travis/dry-rb/dry-container.svg)][travis]
11
- [![Code Climate](https://img.shields.io/codeclimate/github/dry-rb/dry-container.svg)][code_climate]
12
- [![Test Coverage](https://img.shields.io/codeclimate/coverage/github/dry-rb/dry-container.svg)][code_climate]
12
+ [![Maintainability](https://api.codeclimate.com/v1/badges/97faf9446cb5811100e7/maintainability)][maintainability]
13
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/97faf9446cb5811100e7/test_coverage)][test_coverage]
13
14
  [![API Documentation Coverage](http://inch-ci.org/github/dry-rb/dry-container.svg)][inch]
14
15
 
15
-
16
16
  A simple, configurable container implemented in Ruby.
17
17
 
18
- ## Synopsis
19
-
20
- ### Brief Example
21
-
22
- ```ruby
23
- container = Dry::Container.new
24
- container.register(:parrot) { |a| puts a }
25
-
26
- parrot = container.resolve(:parrot)
27
- parrot.call("Hello World")
28
- # Hello World
29
- # => nil
30
- ```
31
-
32
- See [Dry::AutoInject Usage](https://github.com/dry-rb/dry-auto_inject#usage) for additional details.
33
-
34
- ### Detailed Example
35
-
36
- ```ruby
37
- User = Struct.new(:name, :email)
38
-
39
- data_store = Concurrent::Map.new.tap do |ds|
40
- ds[:users] = Concurrent::Array.new
41
- end
42
-
43
- # Initialize container
44
- container = Dry::Container.new
45
-
46
- # Register an item with the container to be resolved later
47
- container.register(:data_store, data_store)
48
- container.register(:user_repository, -> { container.resolve(:data_store)[:users] })
49
-
50
- # Resolve an item from the container
51
- container.resolve(:user_repository) << User.new('Jack', 'jack@dry-container.com')
52
- # You can also resolve with []
53
- container[:user_repository] << User.new('Jill', 'jill@dry-container.com')
54
- # => [
55
- # #<struct User name="Jack", email="jack@dry-container.com">,
56
- # #<struct User name="Jill", email="jill@dry-container.com">
57
- # ]
58
-
59
- # If you wish to register an item that responds to call but don't want it to be
60
- # called when resolved, you can use the options hash
61
- container.register(:proc, -> { :result }, call: false)
62
- container.resolve(:proc)
63
- # => #<Proc:0x007fa75e652c98@(irb):25 (lambda)>
64
-
65
- # You can also register using a block
66
- container.register(:item) do
67
- :result
68
- end
69
- container.resolve(:item)
70
- # => :result
71
-
72
- container.register(:block, call: false) do
73
- :result
74
- end
75
- container.resolve(:block)
76
- # => #<Proc:0x007fa75e6830f0@(irb):36>
77
-
78
- # You can also register items under namespaces using the #namespace method
79
- container.namespace('repositories') do
80
- namespace('checkout') do
81
- register('orders') { Concurrent::Array.new }
82
- end
83
- end
84
- container.resolve('repositories.checkout.orders')
85
- # => []
86
-
87
- # Or import a namespace
88
- ns = Dry::Container::Namespace.new('repositories') do
89
- namespace('authentication') do
90
- register('users') { Concurrent::Array.new }
91
- end
92
- end
93
- container.import(ns)
94
- container.resolve('repositories.authentication.users')
95
- # => []
96
-
97
- # You can also register a block that is used to initialize a dependency and
98
- # then memoize it, allowing several dependencies to be added without
99
- # enforcing an instantiation order
100
- class MessagePrinter
101
- def initialize(container)
102
- @message = container.resolve(:message)
103
- @time = Time.now
104
- end
105
-
106
- def print
107
- puts "#{@message} at #{@time}"
108
- end
109
- end
110
-
111
- container.register(:message_printer, -> { MessagePrinter.new(container) }, memoize: true)
112
- container.register(:message, 'Hello, world!')
113
- container.resolve(:message_printer).print
114
- # => Hello, world! at 2016-08-30 05:32:12 -0700
115
-
116
- # Same instance is reused next time
117
- container.resolve(:message_printer).print
118
- # => Hello, world! at 2016-08-30 05:32:12 -0700
119
- ```
120
-
121
- You can also get container behaviour at both the class and instance level via the mixin:
122
-
123
- ```ruby
124
- class Container
125
- extend Dry::Container::Mixin
126
- end
127
- Container.register(:item, :my_item)
128
- Container.resolve(:item)
129
- # => :my_item
130
-
131
- class ContainerObject
132
- include Dry::Container::Mixin
133
- end
134
- container = ContainerObject.new
135
- container.register(:item, :my_item)
136
- container.resolve(:item)
137
- # => :my_item
138
- ```
139
- ### Using a custom registry/resolver
140
-
141
- You can configure how items are registered and resolved from the container:
142
-
143
- ```ruby
144
- Dry::Container.configure do |config|
145
- config.registry = ->(container, key, item, options) { container[key] = item }
146
- config.resolver = ->(container, key) { container[key] }
147
- end
148
-
149
- class Container
150
- extend Dry::Container::Mixin
151
-
152
- configure do |config|
153
- config.registry = ->(container, key, item, options) { container[key] = item }
154
- config.resolver = ->(container, key) { container[key] }
155
- end
156
- end
157
-
158
- class ContainerObject
159
- include Dry::Container::Mixin
160
-
161
- configure do |config|
162
- config.registry = ->(container, key, item, options) { container[key] = item }
163
- config.resolver = ->(container, key) { container[key] }
164
- end
165
- end
166
- ```
18
+ ## Links
167
19
 
168
- This allows you to customise the behaviour of Dry::Container, for example, the default registry (Dry::Container::Registry) will raise a Dry::Container::Error exception if you try to register under a key that is already used, you may want to just overwrite the existing value in that scenario, configuration allows you to do so.
20
+ [Documentation](http://dry-rb.org/gems/dry-container/)
169
21
 
170
22
  ## License
171
23
 
@@ -1,4 +1,3 @@
1
- # coding: utf-8
2
1
  require File.expand_path('../lib/dry/container/version', __FILE__)
3
2
 
4
3
  Gem::Specification.new do |spec|
@@ -7,7 +6,7 @@ Gem::Specification.new do |spec|
7
6
  spec.authors = ['Andy Holland']
8
7
  spec.email = ['andyholland1991@aol.com']
9
8
  spec.summary = 'A simple container intended for use as an IoC container'
10
- spec.homepage = 'https://github.com/dryrb/dry-container'
9
+ spec.homepage = 'https://github.com/dry-rb/dry-container'
11
10
  spec.license = 'MIT'
12
11
 
13
12
  spec.files = `git ls-files -z`.split("\x0")
@@ -15,7 +14,7 @@ Gem::Specification.new do |spec|
15
14
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
15
  spec.require_paths = ['lib']
17
16
 
18
- spec.required_ruby_version = ">= 2.0.0"
17
+ spec.required_ruby_version = '>= 2.3.0'
19
18
 
20
19
  spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
21
20
  spec.add_runtime_dependency 'dry-configurable', '~> 0.1', '>= 0.1.3'
data/lib/dry/container.rb CHANGED
@@ -1,7 +1,5 @@
1
- require 'concurrent'
2
1
  require 'dry-configurable'
3
2
  require 'dry/container/error'
4
- require 'dry/container/item'
5
3
  require 'dry/container/namespace'
6
4
  require 'dry/container/registry'
7
5
  require 'dry/container/resolver'
@@ -1,6 +1,6 @@
1
1
  module Dry
2
2
  class Container
3
3
  # @api public
4
- class Error < ::StandardError; end
4
+ Error = Class.new(StandardError)
5
5
  end
6
6
  end
@@ -1,42 +1,47 @@
1
1
  module Dry
2
2
  class Container
3
- # Container class
3
+ # Base class to abstract Memoizable and Callable implementations
4
+ #
5
+ # @api abstract
4
6
  #
5
- # @private
6
7
  class Item
7
- attr_reader :item, :options, :memoize, :memoize_mutex
8
+ # @return [Mixed] the item to be solved later
9
+ attr_reader :item
10
+
11
+ # @return [Hash] the options to memoize, call or no.
12
+ attr_reader :options
8
13
 
14
+ # @api abstract
9
15
  def initialize(item, options = {})
10
16
  @item = item
11
17
  @options = {
12
18
  call: item.is_a?(::Proc) && item.parameters.empty?
13
19
  }.merge(options)
14
-
15
- if options[:memoize] == true
16
- raise(
17
- ::Dry::Container::Error,
18
- 'Memoize only supported for a block or a proc'
19
- ) unless item.is_a?(::Proc)
20
- @memoize = true
21
- @memoize_mutex = ::Mutex.new
22
- end
23
20
  end
24
21
 
22
+ # @api abstract
25
23
  def call
26
- return memoized_item if memoize
24
+ raise NotImplementedError
25
+ end
27
26
 
28
- if options[:call] == true
29
- item.call
30
- else
31
- item
32
- end
27
+ # @private
28
+ def value?
29
+ !callable?
33
30
  end
34
31
 
35
- private
32
+ # @private
33
+ def callable?
34
+ options[:call]
35
+ end
36
36
 
37
- def memoized_item
38
- memoize_mutex.synchronize do
39
- @memoized_item ||= item.call
37
+ # Build a new item with transformation applied
38
+ #
39
+ # @private
40
+ def map(func)
41
+ if callable?
42
+ self.class.new(-> { func.(item.call) }, options)
43
+ else
44
+ self.class.new(func.(item), options)
40
45
  end
41
46
  end
42
47
  end
@@ -0,0 +1,20 @@
1
+ require 'dry/container/item'
2
+
3
+ module Dry
4
+ class Container
5
+ class Item
6
+ # Callable class to returns a item call
7
+ #
8
+ # @api public
9
+ #
10
+ class Callable < Item
11
+ # Returns the result of item call or item
12
+ #
13
+ # @return [Mixed]
14
+ def call
15
+ callable? ? item.call : item
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ require 'dry/container/item/memoizable'
2
+ require 'dry/container/item/callable'
3
+
4
+ module Dry
5
+ class Container
6
+ class Item
7
+ # Factory for create an Item to register inside of container
8
+ #
9
+ # @api public
10
+ class Factory
11
+ # Creates an Item Memoizable or Callable
12
+ # @param [Mixed] item
13
+ # @param [Hash] options
14
+ #
15
+ # @raise [Dry::Container::Error]
16
+ #
17
+ # @return [Dry::Container::Item::Base]
18
+ def call(item, options = {})
19
+ options[:memoize] ? Memoizable.new(item, options) : Callable.new(item, options)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,49 @@
1
+ require 'dry/container/item'
2
+
3
+ module Dry
4
+ class Container
5
+ class Item
6
+ # Memoizable class to store and execute item calls
7
+ #
8
+ # @api public
9
+ #
10
+ class Memoizable < Item
11
+ # @return [Mutex] the stored mutex
12
+ attr_reader :memoize_mutex
13
+
14
+ # Returns a new Memoizable instance
15
+ #
16
+ # @param [Mixed] item
17
+ # @param [Hash] options
18
+ #
19
+ # @raise [Dry::Container::Error]
20
+ #
21
+ # @return [Dry::Container::Item::Base]
22
+ def initialize(item, options = {})
23
+ super
24
+ raise_not_supported_error unless callable?
25
+
26
+ @memoize_mutex = ::Mutex.new
27
+ end
28
+
29
+ # Returns the result of item call using a syncronized mutex
30
+ #
31
+ # @return [Dry::Container::Item::Base]
32
+ def call
33
+ memoize_mutex.synchronize do
34
+ @memoized_item ||= item.call
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # @private
41
+ def raise_not_supported_error
42
+ raise ::Dry::Container::Error, 'Memoize only supported for a block or a proc'.freeze
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+
@@ -1,8 +1,14 @@
1
+
2
+ require 'concurrent/hash'
3
+
1
4
  module Dry
2
5
  class Container
3
- PREFIX_NAMESPACE = ->(namespace, key, config) do
6
+ PREFIX_NAMESPACE = lambda do |namespace, key, config|
4
7
  [namespace, key].join(config.namespace_separator)
5
8
  end
9
+
10
+ EMPTY_HASH = {}.freeze
11
+
6
12
  # Mixin to expose Inversion of Control (IoC) container behaviour
7
13
  #
8
14
  # @example
@@ -87,7 +93,7 @@ module Dry
87
93
  # @return [Dry::Container::Mixin] self
88
94
  #
89
95
  # @api public
90
- def register(key, contents = nil, options = {}, &block)
96
+ def register(key, contents = nil, options = EMPTY_HASH, &block)
91
97
  if block_given?
92
98
  item = block
93
99
  options = contents if contents.is_a?(::Hash)
@@ -104,12 +110,15 @@ module Dry
104
110
  #
105
111
  # @param [Mixed] key
106
112
  # The key for the item you wish to resolve
113
+ # @yield
114
+ # Fallback block to call when a key is missing. Its result will be returned
115
+ # @yieldparam [Mixed] key Missing key
107
116
  #
108
117
  # @return [Mixed]
109
118
  #
110
119
  # @api public
111
- def resolve(key)
112
- config.resolver.call(_container, key)
120
+ def resolve(key, &block)
121
+ config.resolver.call(_container, key, &block)
113
122
  end
114
123
 
115
124
  # Resolve an item from the container
@@ -129,8 +138,7 @@ module Dry
129
138
  #
130
139
  # @param [Dry::Container] other
131
140
  # The other container to merge in
132
- # @param [Hash] options
133
- # @option options [Symbol] :namespace
141
+ # @param [Symbol, nil] namespace
134
142
  # Namespace to prefix other container items with, defaults to nil
135
143
  #
136
144
  # @return [Dry::Container::Mixin] self
@@ -150,7 +158,7 @@ module Dry
150
158
  self
151
159
  end
152
160
 
153
- # Check whether an items is registered under the given key
161
+ # Check whether an item is registered under the given key
154
162
  #
155
163
  # @param [Mixed] key
156
164
  # The key you wish to check for registration with
@@ -198,6 +206,29 @@ module Dry
198
206
  config.resolver.each(_container, &block)
199
207
  end
200
208
 
209
+ # Decorates an item from the container with specified decorator
210
+ #
211
+ # @return [Dry::Container::Mixin] self
212
+ #
213
+ # @api public
214
+ def decorate(key, with: nil, &block)
215
+ key = key.to_s
216
+ original = _container.delete(key) do
217
+ raise Error, "Nothing registered with the key #{key.inspect}"
218
+ end
219
+
220
+ if with.is_a?(Class)
221
+ decorator = with.method(:new)
222
+ elsif block.nil? && !with.respond_to?(:call)
223
+ raise Error, "Decorator needs to be a Class, block, or respond to the `call` method"
224
+ else
225
+ decorator = with || block
226
+ end
227
+
228
+ _container[key] = original.map(decorator)
229
+ self
230
+ end
231
+
201
232
  # Evaluate block and register items in namespace
202
233
  #
203
234
  # @param [Mixed] namespace
@@ -231,10 +262,35 @@ module Dry
231
262
  self
232
263
  end
233
264
 
265
+ # Freeze the container. Nothing can be registered after freezing
266
+ #
267
+ # @api public
268
+ def freeze
269
+ super
270
+ _container.freeze
271
+ self
272
+ end
273
+
234
274
  # @private no, really
235
275
  def _container
236
276
  @_container
237
277
  end
278
+
279
+ # @api public
280
+ def dup
281
+ copy = super
282
+ copy.instance_variable_set(:@_container, _container.dup)
283
+ copy
284
+ end
285
+
286
+ # @api public
287
+ def clone
288
+ copy = super
289
+ unless copy.frozen?
290
+ copy.instance_variable_set(:@_container, _container.dup)
291
+ end
292
+ copy
293
+ end
238
294
  end
239
295
  end
240
296
  end
@@ -1,3 +1,5 @@
1
+ require 'delegate'
2
+
1
3
  module Dry
2
4
  class Container
3
5
  # @api private
@@ -43,6 +45,10 @@ module Dry
43
45
  self
44
46
  end
45
47
 
48
+ def resolve(key)
49
+ super(namespaced(key))
50
+ end
51
+
46
52
  private
47
53
 
48
54
  def namespaced(key)
@@ -1,3 +1,5 @@
1
+ require 'dry/container/item/factory'
2
+
1
3
  module Dry
2
4
  class Container
3
5
  # Default registry for registering items with the container
@@ -34,9 +36,13 @@ module Dry
34
36
  raise Error, "There is already an item registered with the key #{key.inspect}"
35
37
  end
36
38
 
37
- container[key] = ::Dry::Container::Item.new(item, options)
39
+ container[key] = factory.call(item, options)
38
40
  end
39
41
  end
42
+
43
+ def factory
44
+ @factory ||= ::Dry::Container::Item::Factory.new
45
+ end
40
46
  end
41
47
  end
42
48
  end
@@ -10,16 +10,24 @@ module Dry
10
10
  # The container
11
11
  # @param [Mixed] key
12
12
  # The key for the item you wish to resolve
13
+ # @yield
14
+ # Fallback block to call when a key is missing. Its result will be returned
15
+ # @yieldparam [Mixed] key Missing key
13
16
  #
14
17
  # @raise [Dry::Conainer::Error]
15
- # If the given key is not registered with the container
18
+ # If the given key is not registered with the container (and no block provided)
19
+ #
16
20
  #
17
21
  # @return [Mixed]
18
22
  #
19
23
  # @api public
20
24
  def call(container, key)
21
25
  item = container.fetch(key.to_s) do
22
- raise Error, "Nothing registered with the key #{key.inspect}"
26
+ if block_given?
27
+ return yield(key)
28
+ else
29
+ raise Error, "Nothing registered with the key #{key.inspect}"
30
+ end
23
31
  end
24
32
 
25
33
  item.call
@@ -48,7 +56,6 @@ module Dry
48
56
  container.keys
49
57
  end
50
58
 
51
-
52
59
  # Calls block once for each key in container, passing the key as a parameter.
53
60
  #
54
61
  # If no block is given, an enumerator is returned instead.
@@ -5,12 +5,16 @@ module Dry
5
5
  #
6
6
  # @api public
7
7
  def resolve(key)
8
- _stubs.fetch(key) { super }
8
+ _stubs.fetch(key.to_s) { super }
9
9
  end
10
10
 
11
11
  # Add a stub to the container
12
12
  def stub(key, value, &block)
13
- _stubs[key] = value
13
+ unless key?(key)
14
+ raise ArgumentError, "cannot stub #{ key.to_s.inspect } - no such key in container"
15
+ end
16
+
17
+ _stubs[key.to_s] = value
14
18
 
15
19
  if block
16
20
  yield
@@ -23,11 +27,12 @@ module Dry
23
27
  # Remove stubbed keys from the container
24
28
  def unstub(*keys)
25
29
  keys = _stubs.keys if keys.empty?
26
- keys.each { |key| _stubs.delete(key) }
30
+ keys.each { |key| _stubs.delete(key.to_s) }
27
31
  end
28
32
 
29
33
  # Stubs have already been enabled turning this into a noop
30
34
  def enable_stubs!
35
+ # DO NOTHING
31
36
  end
32
37
 
33
38
  private
@@ -1,6 +1,6 @@
1
1
  module Dry
2
2
  class Container
3
3
  # @api public
4
- VERSION = '0.6.0'.freeze
4
+ VERSION = '0.7.2'.freeze
5
5
  end
6
6
  end
@@ -25,7 +25,7 @@ RSpec.describe Dry::Container::Mixin do
25
25
  end
26
26
 
27
27
  it 'does not fail on missing member variable' do
28
- expect { container.register :key, ->{} }.to_not raise_error
28
+ expect { container.register :key, -> {} }.to_not raise_error
29
29
  end
30
30
  end
31
31
  end
data/spec/spec_helper.rb CHANGED
@@ -1,11 +1,23 @@
1
- if ENV['COVERAGE'] == 'true' && RUBY_ENGINE == 'ruby' && RUBY_VERSION == '2.3.1'
2
- require 'simplecov'
1
+ # require 'pathname'
3
2
 
4
- SimpleCov.start do
5
- add_filter '/spec/'
3
+ if RUBY_ENGINE == 'ruby' && ENV['COVERAGE'] == 'true'
4
+ require 'yaml'
5
+ rubies = YAML.load(File.read(File.join(__dir__, '..', '.travis.yml')))['rvm']
6
+ latest_mri = rubies.select { |v| v =~ /\A\d+\.\d+.\d+\z/ }.max
7
+
8
+ if RUBY_VERSION == latest_mri
9
+ require 'simplecov'
10
+ SimpleCov.start do
11
+ add_filter '/spec/'
12
+ end
6
13
  end
7
14
  end
8
15
 
16
+ begin
17
+ require 'pry-byebug'
18
+ rescue LoadError
19
+ end
20
+
9
21
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
10
22
  RSpec.configure do |config|
11
23
  config.expect_with :rspec do |expectations|
@@ -43,7 +55,7 @@ RSpec.configure do |config|
43
55
  # Allows RSpec to persist some state between runs in order to support
44
56
  # the `--only-failures` and `--next-failure` CLI options. We recommend
45
57
  # you configure your source control system to ignore this file.
46
- config.example_status_persistence_file_path = "spec/examples.txt"
58
+ config.example_status_persistence_file_path = 'spec/examples.txt'
47
59
 
48
60
  # Limits the available syntax to the non-monkey patched syntax that is
49
61
  # recommended. For more details, see:
@@ -130,7 +130,7 @@ RSpec.shared_examples 'a container' do
130
130
  end
131
131
 
132
132
  it 'does not call a proc on resolving if one accepts an arbitrary number of keyword arguments' do
133
- container.register(:item) { |**kw| 'item' }
133
+ container.register(:item) { |*| 'item' }
134
134
 
135
135
  expect(container.resolve(:item)).to be_a_kind_of Proc
136
136
  expect(container.resolve(:item).call).to eq('item')
@@ -383,6 +383,98 @@ RSpec.shared_examples 'a container' do
383
383
  end
384
384
  end
385
385
 
386
+ describe '#decorate' do
387
+ require 'delegate'
388
+
389
+ let(:key) { :key }
390
+ let(:decorated_class_spy) { spy(:decorated_class_spy) }
391
+ let(:decorated_class) { Class.new }
392
+
393
+ context 'for callable item' do
394
+ before do
395
+ allow(decorated_class_spy).to receive(:new) { decorated_class.new }
396
+ container.register(key, memoize: memoize) { decorated_class_spy.new }
397
+ container.decorate(key, with: SimpleDelegator)
398
+ end
399
+
400
+ context 'memoize false' do
401
+ let(:memoize) { false }
402
+
403
+ it 'does not call the block until the key is resolved' do
404
+ expect(decorated_class_spy).not_to have_received(:new)
405
+ container.resolve(key)
406
+ expect(decorated_class_spy).to have_received(:new)
407
+ end
408
+
409
+ specify do
410
+ expect(container[key]).to be_instance_of(SimpleDelegator)
411
+ expect(container[key].__getobj__).to be_instance_of(decorated_class)
412
+ expect(container[key]).not_to be(container[key])
413
+ expect(container[key].__getobj__).not_to be(container[key].__getobj__)
414
+ end
415
+ end
416
+
417
+ context 'memoize true' do
418
+ let(:memoize) { true }
419
+
420
+ specify do
421
+ expect(container[key]).to be_instance_of(SimpleDelegator)
422
+ expect(container[key].__getobj__).to be_instance_of(decorated_class)
423
+ expect(container[key]).to be(container[key])
424
+ end
425
+ end
426
+ end
427
+
428
+ context 'for not callable item' do
429
+ describe 'wrapping' do
430
+ before do
431
+ container.register(key, call: false) { "value" }
432
+ container.decorate(key, with: SimpleDelegator)
433
+ end
434
+
435
+ it 'expected to be an instance of SimpleDelegator' do
436
+ expect(container.resolve(key)).to be_instance_of(SimpleDelegator)
437
+ expect(container.resolve(key).__getobj__.call).to eql("value")
438
+ end
439
+ end
440
+
441
+ describe 'memoization' do
442
+ before do
443
+ @called = 0
444
+ container.register(key, 'value')
445
+
446
+ container.decorate(key) do |value|
447
+ @called += 1
448
+ "<#{value}>"
449
+ end
450
+ end
451
+
452
+ it 'decorates static value only once' do
453
+ expect(container.resolve(key)).to eql('<value>')
454
+ expect(container.resolve(key)).to eql('<value>')
455
+ expect(@called).to be(1)
456
+ end
457
+ end
458
+ end
459
+
460
+ context 'with an instance as a decorator' do
461
+ let(:decorator) do
462
+ double.tap do |decorator|
463
+ allow(decorator).to receive(:call) { |input| "decorated #{input}" }
464
+ end
465
+ end
466
+
467
+ before do
468
+ container.register(key) { "value" }
469
+ container.decorate(key, with: decorator)
470
+ end
471
+
472
+ it 'expected to pass original value to decorator#call method' do
473
+ expect(container.resolve(key)).to eq("decorated value")
474
+ end
475
+ end
476
+ end
477
+
386
478
  describe 'namespace' do
387
479
  context 'when block does not take arguments' do
388
480
  before do
@@ -427,6 +519,21 @@ RSpec.shared_examples 'a container' do
427
519
  is_expected.to eq(3)
428
520
  end
429
521
  end
522
+
523
+ context 'with nesting and when block takes arguments' do
524
+ before do
525
+ container.namespace('one') do |c|
526
+ c.register('two', 2)
527
+ c.register('three', c.resolve('two'))
528
+ end
529
+ end
530
+
531
+ subject! { container.resolve('one.three') }
532
+
533
+ it 'resolves items relative to the namespace' do
534
+ is_expected.to eq(2)
535
+ end
536
+ end
430
537
  end
431
538
 
432
539
  describe 'import' do
@@ -499,5 +606,59 @@ RSpec.shared_examples 'a container' do
499
606
  expect(container.resolve(:item)).to eql('item')
500
607
  end
501
608
  end
609
+
610
+ describe 'mixing Strings and Symbols' do
611
+ it do
612
+ container.stub(:item, 'stub')
613
+ expect(container.resolve('item')).to eql('stub')
614
+ end
615
+ end
616
+
617
+ it 'raises an error when key is missing' do
618
+ expect { container.stub(:non_existing, 'something') }.
619
+ to raise_error(ArgumentError, 'cannot stub "non_existing" - no such key in container')
620
+ end
621
+ end
622
+
623
+ describe '.freeze' do
624
+ before do
625
+ container.register(:foo, 'bar')
626
+ end
627
+
628
+ it 'allows to freeze a container so that nothing can be registered later' do
629
+ container.freeze
630
+ error = RUBY_VERSION >= '2.5' ? FrozenError : RuntimeError
631
+ expect { container.register(:baz, 'quux') }.to raise_error(error)
632
+ expect(container).to be_frozen
633
+ end
634
+
635
+ it 'returns self back' do
636
+ expect(container.freeze).to be(container)
637
+ end
638
+ end
639
+
640
+ describe '.dup' do
641
+ it "returns a copy that doesn't share registered keys with the parent" do
642
+ container.dup.register(:foo, 'bar')
643
+ expect(container.key?(:foo)).to be false
644
+ end
645
+ end
646
+
647
+ describe '.clone' do
648
+ it "returns a copy that doesn't share registered keys with the parent" do
649
+ container.clone.register(:foo, 'bar')
650
+ expect(container.key?(:foo)).to be false
651
+ end
652
+
653
+ it 're-uses frozen container' do
654
+ expect(container.freeze.clone).to be_frozen
655
+ expect(container.clone._container).to be(container._container)
656
+ end
657
+ end
658
+
659
+ describe '.resolve' do
660
+ it 'accepts a fallback block' do
661
+ expect(container.resolve('missing') { :fallback }).to be(:fallback)
662
+ end
502
663
  end
503
664
  end
metadata CHANGED
@@ -1,31 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-container
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Holland
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-09 00:00:00.000000000 Z
11
+ date: 2019-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: concurrent-ruby
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
18
  version: '1.0'
20
- type: :runtime
19
+ name: concurrent-ruby
21
20
  prerelease: false
21
+ type: :runtime
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: dry-configurable
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - "~>"
@@ -34,8 +33,9 @@ dependencies:
34
33
  - - ">="
35
34
  - !ruby/object:Gem::Version
36
35
  version: 0.1.3
37
- type: :runtime
36
+ name: dry-configurable
38
37
  prerelease: false
38
+ type: :runtime
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - "~>"
@@ -45,48 +45,48 @@ dependencies:
45
45
  - !ruby/object:Gem::Version
46
46
  version: 0.1.3
47
47
  - !ruby/object:Gem::Dependency
48
- name: bundler
49
48
  requirement: !ruby/object:Gem::Requirement
50
49
  requirements:
51
50
  - - ">="
52
51
  - !ruby/object:Gem::Version
53
52
  version: '0'
54
- type: :development
53
+ name: bundler
55
54
  prerelease: false
55
+ type: :development
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0'
61
61
  - !ruby/object:Gem::Dependency
62
- name: rake
63
62
  requirement: !ruby/object:Gem::Requirement
64
63
  requirements:
65
64
  - - ">="
66
65
  - !ruby/object:Gem::Version
67
66
  version: '0'
68
- type: :development
67
+ name: rake
69
68
  prerelease: false
69
+ type: :development
70
70
  version_requirements: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - ">="
73
73
  - !ruby/object:Gem::Version
74
74
  version: '0'
75
75
  - !ruby/object:Gem::Dependency
76
- name: rspec
77
76
  requirement: !ruby/object:Gem::Requirement
78
77
  requirements:
79
78
  - - ">="
80
79
  - !ruby/object:Gem::Version
81
80
  version: '0'
82
- type: :development
81
+ name: rspec
83
82
  prerelease: false
83
+ type: :development
84
84
  version_requirements: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - ">="
87
87
  - !ruby/object:Gem::Version
88
88
  version: '0'
89
- description:
89
+ description:
90
90
  email:
91
91
  - andyholland1991@aol.com
92
92
  executables: []
@@ -100,6 +100,7 @@ files:
100
100
  - ".rubocop_todo.yml"
101
101
  - ".travis.yml"
102
102
  - CHANGELOG.md
103
+ - CONTRIBUTING.md
103
104
  - Gemfile
104
105
  - LICENSE
105
106
  - README.md
@@ -109,6 +110,9 @@ files:
109
110
  - lib/dry/container.rb
110
111
  - lib/dry/container/error.rb
111
112
  - lib/dry/container/item.rb
113
+ - lib/dry/container/item/callable.rb
114
+ - lib/dry/container/item/factory.rb
115
+ - lib/dry/container/item/memoizable.rb
112
116
  - lib/dry/container/mixin.rb
113
117
  - lib/dry/container/namespace.rb
114
118
  - lib/dry/container/namespace_dsl.rb
@@ -121,11 +125,11 @@ files:
121
125
  - spec/integration/mixin_spec.rb
122
126
  - spec/spec_helper.rb
123
127
  - spec/support/shared_examples/container.rb
124
- homepage: https://github.com/dryrb/dry-container
128
+ homepage: https://github.com/dry-rb/dry-container
125
129
  licenses:
126
130
  - MIT
127
131
  metadata: {}
128
- post_install_message:
132
+ post_install_message:
129
133
  rdoc_options: []
130
134
  require_paths:
131
135
  - lib
@@ -133,16 +137,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
133
137
  requirements:
134
138
  - - ">="
135
139
  - !ruby/object:Gem::Version
136
- version: 2.0.0
140
+ version: 2.3.0
137
141
  required_rubygems_version: !ruby/object:Gem::Requirement
138
142
  requirements:
139
143
  - - ">="
140
144
  - !ruby/object:Gem::Version
141
145
  version: '0'
142
146
  requirements: []
143
- rubyforge_project:
144
- rubygems_version: 2.5.1
145
- signing_key:
147
+ rubyforge_project:
148
+ rubygems_version: 2.7.9
149
+ signing_key:
146
150
  specification_version: 4
147
151
  summary: A simple container intended for use as an IoC container
148
152
  test_files: