dry-component 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ee78e672954f38457a8469c522b169caff7433ae
4
- data.tar.gz: 8b649eed1db04abef81ac6d0512f38bbafd26cb3
3
+ metadata.gz: 6fd0b3400c225e2c1f856aff61cf4e554b8f9fc5
4
+ data.tar.gz: b9ead2e64ad32dee264b29973c932a1394dc6775
5
5
  SHA512:
6
- metadata.gz: a0bf880fd5c645e2fa85c40061f66a9a8da31be70f0fadff877b15cccede26560215b52895fa9e1a443609f8db9dc587b00c821e1d75d0768675f98d28d978cd
7
- data.tar.gz: 4ac3f8c0f089975e808736577bfb0e8fc3fcafebfeb6ca8ed440d4a060d9c2ef8eaadfc8801c3609e6991926bbd08fafc8fd4a9ce5aa4a55232f626f33a00843
6
+ metadata.gz: 6b38cbe04c32bd241421fe438551c8fc3d51ca9e23332f222f93f14c1afe33a4574848418298db8eeec621a679000d5eea01e42e2f389e5cfe43b1dcba339460
7
+ data.tar.gz: 06af0944d5af7614a50e381b2ceb5efd850ae10519f1deb8487e9ed7f19b3a6ec413446f2ec32f52983b11a3e718b3f22c9b3189c4ce95a70f6aac97a9ed42a7
@@ -8,7 +8,8 @@ rvm:
8
8
  - 2.0
9
9
  - 2.1
10
10
  - 2.2
11
- - rbx-2
11
+ - 2.3.1
12
+ - rbx
12
13
  - jruby-9000
13
14
  - ruby-head
14
15
  - jruby-head
@@ -1,3 +1,39 @@
1
- # v0.0.1 2015-12-24
1
+ # 0.1.0 - 2016-06-07
2
+
3
+ ## Added
4
+
5
+ * Provide a dependency injector as an `Inject` constant inside any subclass of `Dry::Component::Container`. This injector supports all of `dry-auto_inject`'s default injection strategies, and will lazily load any dependencies as they are injected. It also supports arbitrarily switching strategies, so they can be used in different classes as required (e.g. `include MyComponent::Inject.args["dep"]`) (timriley)
6
+ * Support aliased dependency names when calling the injector object (e.g. `MyComponent::Inject[foo: "my_app.foo", bar: "another.thing"]`) (timriley)
7
+ * Allow a custom dependency loader to be set on a container via its config (AMHOL)
8
+ ```ruby
9
+ class MyContainer < Dry::Component::Container
10
+ configure do |config|
11
+ # other config
12
+ config.loader = MyLoader
13
+ end
14
+ end
15
+ ```
16
+
17
+ ## Changed
18
+
19
+ * `Container.boot` now only makes a simple `require` for the boot file (solnic)
20
+ * Container object is passed to `Container.finalize` blocks (solnic)
21
+ * Allow `Pathname` objects passed to `Container.require` (solnic)
22
+ * Support lazily loading missing dependencies from imported containers (solnic)
23
+ * `Container.import_module` renamed to `.injector` (timriley)
24
+ * Default injection strategy is now `kwargs`, courtesy of the new dry-auto_inject default (timriley)
25
+
26
+ # 0.0.2 - 2015-12-24
27
+
28
+ ## Added
29
+
30
+ * Containers have a `name` setting (solnic)
31
+ * Containers can be imported into one another (solnic)
32
+
33
+ ## Changed
34
+
35
+ * Container name is used to determine the name of its config file (solnic)
36
+
37
+ # 0.0.1 - 2015-12-24
2
38
 
3
39
  First public release, extracted from rodakase project
data/README.md CHANGED
@@ -1,217 +1,14 @@
1
- # dry-component <a href="https://gitter.im/dryrb/chat" target="_blank">![Join the chat at https://gitter.im/dryrb/chat](https://badges.gitter.im/Join%20Chat.svg)</a>
1
+ # dry-component <a href="https://gitter.im/dry-rb/chat" target="_blank">![Join the chat at https://gitter.im/dry-rb/chat](https://badges.gitter.im/Join%20Chat.svg)</a>
2
2
 
3
3
  <a href="https://rubygems.org/gems/dry-component" target="_blank">![Gem Version](https://badge.fury.io/rb/dry-component.svg)</a>
4
- <a href="https://travis-ci.org/dryrb/dry-component" target="_blank">![Build Status](https://travis-ci.org/dryrb/dry-component.svg?branch=master)</a>
5
- <a href="https://gemnasium.com/dryrb/dry-component" target="_blank">![Dependency Status](https://gemnasium.com/dryrb/dry-component.svg)</a>
6
- <a href="https://codeclimate.com/github/dryrb/dry-component" target="_blank">![Code Climate](https://codeclimate.com/github/dryrb/dry-component/badges/gpa.svg)</a>
7
- <a href="http://inch-ci.org/github/dryrb/dry-component" target="_blank">![Documentation Status](http://inch-ci.org/github/dryrb/dry-component.svg?branch=master&style=flat)</a>
4
+ <a href="https://travis-ci.org/dry-rb/dry-component" target="_blank">![Build Status](https://travis-ci.org/dry-rb/dry-component.svg?branch=master)</a>
5
+ <a href="https://gemnasium.com/dry-rb/dry-component" target="_blank">![Dependency Status](https://gemnasium.com/dry-rb/dry-component.svg)</a>
6
+ <a href="https://codeclimate.com/github/dry-rb/dry-component" target="_blank">![Code Climate](https://codeclimate.com/github/dry-rb/dry-component/badges/gpa.svg)</a>
7
+ <a href="http://inch-ci.org/github/dry-rb/dry-component" target="_blank">![Documentation Status](http://inch-ci.org/github/dry-rb/dry-component.svg?branch=master&style=flat)</a>
8
8
 
9
- Sane dependency management system allowing you to configure reusable components
10
- in any environment, set up their load-paths, require needed files and instantiate
11
- objects automatically with the ability to have them injected as dependencies.
9
+ ## Links
12
10
 
13
- Originally built for [rodakase](https://github.com/solnic/rodakase) stack, now as
14
- a standalone, small library.
15
-
16
- This is a simple system that relies on very basic mechanisms provided by Ruby,
17
- specifically `require` and managing `$LOAD_PATH`. It does not rely on any magic
18
- like automatic const resolution, it's pretty much the opposite and forces you to
19
- be explicit about dependencies in your applications.
20
-
21
- It does a couple of things for you that are really not something you want to do
22
- yourself:
23
-
24
- * Provides an abstract dependency container implementation
25
- * Handles `$LOAD_PATH` configuration
26
- * Loads needed files using `require`
27
- * Resolves dependencies automatically
28
- * Supports auto-registration of dependencies via file/dir naming conventions
29
- * Provides support for custom configuration loaded from external sources (ie YAML)
30
-
31
- To put it all together, this allows you to configure your system in a way where
32
- you have full control over dependencies and it's very easy to draw the boundaries
33
- between individual components.
34
-
35
- This comes with a bunch of nice benefits:
36
-
37
- * Your system relies on abstractions rather than concrete classes and modules
38
- * It helps in decoupling your code from 3rd party code
39
- * It makes it possible to load components in complete isolation. In example you
40
- can run a single test for a single component and only required files will be
41
- loaded, or you can run a rake task and it will only load the things it needs.
42
- * It opens up doors for better instrumentation and debugging tools
43
-
44
- ## Container
45
-
46
- Main API is the abstract container that you inherit from. It allows you to configure
47
- basic settings and exposes APIs for requiring files easily.
48
-
49
- Let's say you want to define an application container that will provide a logger:
50
-
51
- ``` ruby
52
- require 'dry/component/container'
53
-
54
- class Application < Dry::Component::Container
55
- configure do |config|
56
- config.root = '/my/app'
57
- end
58
- end
59
-
60
- # now you can register a logger
61
- require 'logger'
62
- Application.register('utils.logger', Logger.new($stdout))
63
-
64
- # and access it
65
- Application['utils.logger']
66
- ```
67
-
68
- ## Auto-Registration
69
-
70
- By using simple naming conventions we can automatically register objects within
71
- our container.
72
-
73
- Let's provide a custom logger object and put it under a custom load-path that we
74
- will configure:
75
-
76
- ``` ruby
77
- require 'dry/component/container'
78
-
79
- class Application < Dry::Component::Container
80
- configure do |config|
81
- config.root = '/my/app'
82
-
83
- # we set 'lib' relative to `root` as a path which contains class definitions
84
- # that can be auto-registered
85
- config.auto_register = 'lib'
86
- end
87
-
88
- # this alters $LOAD_PATH hence the `!`
89
- load_paths!('lib')
90
- end
91
-
92
- # under /my/app/lib/logger.rb we put
93
- class Logger
94
- # some neat logger implementation
95
- end
96
-
97
- # we can finalize the container which triggers auto-registration
98
- Application.finalize!
99
-
100
- # the logger becomes available
101
- Application['logger']
102
- ```
103
-
104
- ## Auto-Import Mechanism
105
-
106
- After defining a container, we can use its import module that will inject object
107
- dependencies automatically.
108
-
109
- Let's say we have an object that will need a logger:
110
-
111
- ``` ruby
112
- # let's define an import module
113
- Import = Application.import_module
114
-
115
- # in a class definition you simply specify what it needs
116
- class PostPublisher
117
- include Import['utils.logger']
118
-
119
- def call(post)
120
- # some stuff
121
- logger.debug("post published: #{post}")
122
- end
123
- end
124
- ```
125
-
126
- ## Directory Structure
127
-
128
- You need to provide a specific directory/file structure but names of directories
129
- are configurable. The default is as follows:
130
-
131
- ```
132
- #{root}
133
- |- core
134
- |- boot
135
- # arbitrary files that are automatically loaded on finalization
136
- ```
137
-
138
- ## Booting a Dependency
139
-
140
- In some cases a dependency can be huge, so huge it needs to load some additional
141
- files (often 3rd party code) and it may rely on custom configuration.
142
-
143
- Because of this reason `dry-component` has the concept of booting a dependency.
144
-
145
- The convention is pretty simple. You put files under `boot` directory and use
146
- your container to register dependencies with the ability to postpone finalization.
147
- This gives us a way to define what's needed but load it and boot it on demand.
148
-
149
- Here's a simple example:
150
-
151
- ``` ruby
152
- # under /my/app/boot/heavy_dep.rb
153
-
154
- Application.finalize(:persistence) do
155
- # some 3rd-party dependency
156
- require '3rd-party/database'
157
-
158
- container.register('database') do
159
- # some code which initializes this thing
160
- end
161
- end
162
- ```
163
-
164
- After defining the finalization block our container will not call it until its
165
- own finalization. This means we can require file that defines our container
166
- and ask it to boot *just that one :persistence dependency*:
167
-
168
- ``` ruby
169
- # under /my/app/boot/container.rb
170
- class Application < Dry::Component::Container
171
- configure do |config|
172
- config.root = '/my/app'
173
- end
174
- end
175
-
176
- Application.boot!(:persistence)
177
-
178
- # and now `database` becomes available
179
- Application['database']
180
- ```
181
-
182
- ## Environment & Providing Arbitrary Options
183
-
184
- In most of the systems you need some kind of options for your runtime. Typically
185
- it's provided via ENV vars or a yaml file in development mode. `dry-component`
186
- has a built-in support for this.
187
-
188
- You can simply put a file under `#{root}/config/application.yml` and it will be
189
- loaded:
190
-
191
- ``` yaml
192
- # /my/app/config/application.yml
193
- development:
194
- foo: 'bar'
195
- ```
196
-
197
- Now let's configure our container for a specific env:
198
-
199
- ``` ruby
200
- class Application < Dry::Component::Container
201
- configure('development') do |config|
202
- config.root = '/my/app'
203
- end
204
- end
205
-
206
- # now our application options are available
207
- Application.options.foo # => "bar"
208
- ```
209
-
210
- ## Underlying Tools
211
-
212
- `dry-component` uses [dry-container](https://github.com/dryrb/dry-container) and
213
- [dry-auto_inject](https://github.com/dryrb/dry-auto_inject) under the hood. These
214
- gems are very small and simple with a total 254LOC. Just saying.
11
+ * [Documentation](http://dry-rb.org/gems/dry-component)
215
12
 
216
13
  ## LICENSE
217
14
 
@@ -15,10 +15,9 @@ Gem::Specification.new do |spec|
15
15
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
16
  spec.require_paths = ['lib']
17
17
 
18
- spec.add_runtime_dependency 'memoizable', '~> 0.4'
19
18
  spec.add_runtime_dependency 'inflecto', '>= 0.0.2'
20
- spec.add_runtime_dependency 'dry-container', '~> 0.2', '>= 0.2.7'
21
- spec.add_runtime_dependency 'dry-auto_inject', '~> 0.1'
19
+ spec.add_runtime_dependency 'dry-container', '~> 0.3', '>= 0.3.4'
20
+ spec.add_runtime_dependency 'dry-auto_inject', '~> 0.3'
22
21
  spec.add_runtime_dependency 'dry-configurable', '~> 0.1'
23
22
 
24
23
  spec.add_development_dependency 'bundler'
@@ -3,20 +3,20 @@ require 'yaml'
3
3
  module Dry
4
4
  module Component
5
5
  class Config
6
- extend Dry::Configurable
7
-
8
- def self.load(root, env)
9
- path = root.join('config').join('application.yml')
6
+ def self.load(root, name, env)
7
+ path = root.join('config').join("#{name}.yml")
10
8
 
11
9
  return {} unless File.exist?(path)
12
10
 
13
11
  yaml = YAML.load_file(path)
14
12
 
15
- yaml.fetch(env.to_s).each do |key, value|
16
- setting key.downcase.to_sym, ENV.fetch(key, value)
17
- end
13
+ Class.new do
14
+ extend Dry::Configurable
18
15
 
19
- config
16
+ yaml.fetch(env.to_s).each do |key, value|
17
+ setting key.downcase.to_sym, ENV.fetch(key, value)
18
+ end
19
+ end.config
20
20
  end
21
21
  end
22
22
  end
@@ -1,7 +1,9 @@
1
+ require 'pathname'
1
2
  require 'inflecto'
3
+
2
4
  require 'dry-container'
3
- require 'dry-auto_inject'
4
5
 
6
+ require 'dry/component/injector'
5
7
  require 'dry/component/loader'
6
8
  require 'dry/component/config'
7
9
 
@@ -11,17 +13,24 @@ module Dry
11
13
  extend Dry::Container::Mixin
12
14
 
13
15
  setting :env
16
+ setting :name
14
17
  setting :root, Pathname.pwd.freeze
15
18
  setting :core_dir, 'core'.freeze
16
19
  setting :auto_register
17
20
  setting :options
21
+ setting :loader, Dry::Component::Loader
22
+
23
+ def self.inherited(subclass)
24
+ super
25
+ subclass.const_set :Inject, subclass.injector
26
+ end
18
27
 
19
28
  def self.configure(env = config.env, &block)
20
29
  return self if configured?
21
30
 
22
31
  super() do |config|
23
32
  yield(config) if block
24
- config.options = Config.load(root, env)
33
+ config.options = Config.load(root, config.name, env)
25
34
  end
26
35
 
27
36
  load_paths!(config.core_dir)
@@ -31,12 +40,23 @@ module Dry
31
40
  self
32
41
  end
33
42
 
43
+ def self.import(other)
44
+ case other
45
+ when Dry::Container::Namespace then super
46
+ when Hash then imports.update(other)
47
+ else
48
+ if other < Component::Container
49
+ imports.update(other.config.name => other)
50
+ end
51
+ end
52
+ end
53
+
34
54
  def self.options
35
55
  config.options
36
56
  end
37
57
 
38
58
  def self.finalize(name, &block)
39
- finalizers[name] = block
59
+ finalizers[name] = proc { block.(self) }
40
60
  end
41
61
 
42
62
  def self.configured?
@@ -46,6 +66,10 @@ module Dry
46
66
  def self.finalize!(&_block)
47
67
  yield(self) if block_given?
48
68
 
69
+ imports.each do |ns, container|
70
+ import_container(ns, container.finalize!)
71
+ end
72
+
49
73
  Dir[root.join("#{config.core_dir}/boot/**/*.rb")].each do |path|
50
74
  boot!(File.basename(path, '.rb').to_sym)
51
75
  end
@@ -55,13 +79,8 @@ module Dry
55
79
  freeze
56
80
  end
57
81
 
58
- def self.import_module
59
- auto_inject = Dry::AutoInject(self)
60
-
61
- -> *keys {
62
- keys.each { |key| load_component(key) unless key?(key) }
63
- auto_inject[*keys]
64
- }
82
+ def self.injector
83
+ Injector.new(self)
65
84
  end
66
85
 
67
86
  def self.auto_register!(dir, &_block)
@@ -69,7 +88,7 @@ module Dry
69
88
 
70
89
  Dir["#{root}/#{dir}/**/*.rb"].each do |path|
71
90
  component_path = path.to_s.gsub("#{dir_root}/", '').gsub('.rb', '')
72
- Component.Loader(component_path).tap do |component|
91
+ config.loader.new(component_path).tap do |component|
73
92
  next if key?(component.identifier)
74
93
 
75
94
  Kernel.require component.path
@@ -87,19 +106,22 @@ module Dry
87
106
 
88
107
  def self.boot!(name)
89
108
  check_component_identifier!(name)
109
+
90
110
  return self unless booted?(name)
91
- boot(name)
92
- self
93
- end
94
111
 
95
- def self.boot(name)
96
- require "#{config.core_dir}/boot/#{name}.rb"
112
+ boot(name)
97
113
 
98
114
  finalizers[name].tap do |finalizer|
99
115
  finalizer.() if finalizer
100
116
  end
101
117
 
102
118
  booted[name] = true
119
+
120
+ self
121
+ end
122
+
123
+ def self.boot(name)
124
+ require "#{config.core_dir}/boot/#{name}"
103
125
  end
104
126
 
105
127
  def self.booted?(name)
@@ -108,25 +130,37 @@ module Dry
108
130
 
109
131
  def self.require(*paths)
110
132
  paths.flat_map { |path|
111
- path.include?('*') ? Dir[root.join(path)] : root.join(path)
133
+ path.to_s.include?('*') ? Dir[root.join(path)] : root.join(path)
112
134
  }.each { |path|
113
135
  Kernel.require path.to_s
114
136
  }
115
137
  end
116
138
 
117
139
  def self.load_component(key)
118
- require_component(key) { |klass| register(key) { klass.new } }
140
+ component = config.loader.new(key)
141
+ src_key = component.namespaces[0]
142
+
143
+ if imports.key?(src_key)
144
+ src_container = imports[src_key]
145
+
146
+ src_container.load_component(
147
+ (component.namespaces - [src_key]).map(&:to_s).join('.')
148
+ )
149
+
150
+ import_container(src_key, src_container)
151
+ else
152
+ require_component(component) { |klass| register(key) { klass.new } }
153
+ end
119
154
  end
120
155
 
121
- def self.require_component(key, &block)
122
- component = Component.Loader(key)
156
+ def self.require_component(component, &block)
123
157
  path = load_paths.detect { |p| p.join(component.file).exist? }
124
158
 
125
159
  if path
126
160
  Kernel.require component.path
127
161
  yield(component.constant) if block
128
162
  else
129
- fail ArgumentError, "could not resolve require file for #{key}"
163
+ fail ArgumentError, "could not resolve require file for #{component.identifier}"
130
164
  end
131
165
  end
132
166
 
@@ -155,8 +189,20 @@ module Dry
155
189
  @finalizers ||= {}
156
190
  end
157
191
 
192
+ def self.imports
193
+ @imports ||= {}
194
+ end
195
+
158
196
  private
159
197
 
198
+ def self.import_container(ns, container)
199
+ items = container._container.each_with_object({}) { |(key, item), res|
200
+ res[[ns, key].join(config.namespace_separator)] = item
201
+ }
202
+
203
+ _container.update(items)
204
+ end
205
+
160
206
  def self.auto_register
161
207
  Array(config.auto_register)
162
208
  end
@@ -0,0 +1,63 @@
1
+ require "dry-auto_inject"
2
+
3
+ module Dry
4
+ module Component
5
+ class Injector
6
+ # @api private
7
+ attr_reader :container
8
+
9
+ # @api private
10
+ attr_reader :injector
11
+
12
+ # @api private
13
+ def initialize(container, strategy: :args, strategies_cache: nil)
14
+ @container = container
15
+ @strategies = strategies_cache
16
+
17
+ @injector = if strategy == :args
18
+ Dry::AutoInject(container)
19
+ else
20
+ Dry::AutoInject(container).send(strategy)
21
+ end
22
+ end
23
+
24
+ # @api public
25
+ def [](*deps)
26
+ load_components(*deps)
27
+ injector[*deps]
28
+ end
29
+
30
+ # @api public
31
+ def args
32
+ strategies[:args]
33
+ end
34
+
35
+ # @api public
36
+ def hash
37
+ strategies[:hash]
38
+ end
39
+
40
+ # @api public
41
+ def kwargs
42
+ strategies[:kwargs]
43
+ end
44
+
45
+ private
46
+
47
+ def load_components(*components)
48
+ components = components.dup
49
+ aliases = components.last.is_a?(Hash) ? components.pop : {}
50
+
51
+ (components + aliases.values).each do |key|
52
+ container.load_component(key) unless container.key?(key)
53
+ end
54
+ end
55
+
56
+ def strategies
57
+ @strategies ||= Hash.new do |cache, strategy|
58
+ cache[strategy] = self.class.new(container, strategy: strategy, strategies_cache: cache)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,47 +1,36 @@
1
- require 'memoizable'
2
1
  require 'inflecto'
3
2
 
4
3
  module Dry
5
4
  module Component
6
- def self.Loader(input)
7
- Loader.new(Loader.identifier(input), Loader.path(input))
8
- end
9
-
10
5
  class Loader
11
- include Memoizable
12
-
13
6
  IDENTIFIER_SEPARATOR = '.'.freeze
14
7
  PATH_SEPARATOR = '/'.freeze
15
8
 
16
9
  attr_reader :identifier, :path, :file
17
10
 
18
- def self.identifier(input)
19
- input.to_s.gsub(PATH_SEPARATOR, IDENTIFIER_SEPARATOR)
20
- end
21
-
22
- def self.path(input)
23
- input.to_s.gsub(IDENTIFIER_SEPARATOR, PATH_SEPARATOR)
24
- end
25
-
26
- def initialize(identifier, path)
27
- @identifier = identifier
28
- @path = path
11
+ def initialize(input)
12
+ @identifier = input.to_s.gsub(PATH_SEPARATOR, IDENTIFIER_SEPARATOR)
13
+ @path = input.to_s.gsub(IDENTIFIER_SEPARATOR, PATH_SEPARATOR)
29
14
  @file = "#{path}.rb"
30
15
  end
31
16
 
32
- def name
33
- Inflecto.camelize(path)
17
+ def namespaces
18
+ identifier.split(IDENTIFIER_SEPARATOR).map(&:to_sym)
34
19
  end
35
- memoize :name
36
20
 
37
21
  def constant
38
22
  Inflecto.constantize(name)
39
23
  end
40
- memoize :constant
41
24
 
42
25
  def instance(*args)
43
26
  constant.new(*args)
44
27
  end
28
+
29
+ private
30
+
31
+ def name
32
+ Inflecto.camelize(path)
33
+ end
45
34
  end
46
35
  end
47
36
  end
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module Component
3
- VERSION = '0.0.1'.freeze
3
+ VERSION = '0.1.0'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,5 @@
1
+ class Bar
2
+ def self.call
3
+ "Welcome to my Moe's Tavern!"
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ class Bar
2
+ class Baz
3
+ end
4
+ end
@@ -0,0 +1,2 @@
1
+ class Foo
2
+ end
@@ -0,0 +1,2 @@
1
+ test:
2
+ foo: 'bar'
@@ -0,0 +1,11 @@
1
+ Test::Container.namespace(:test) do |container|
2
+ module Test
3
+ module Bar
4
+ # I shall be booted
5
+ end
6
+ end
7
+
8
+ container.finalize(:bar) do
9
+ container.register(:bar, 'I was finalized')
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ module Test
2
+ class Bar
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module Test
2
+ class Foo
3
+ include Import['test.bar']
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ test:
2
+ foo: 'bar'
@@ -0,0 +1,11 @@
1
+ Test::Container.namespace(:test) do |container|
2
+ module Test
3
+ module Bar
4
+ # I shall be booted
5
+ end
6
+ end
7
+
8
+ container.finalize(:bar) do
9
+ container.register(:bar, 'I was finalized')
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ module Test
2
+ class Dep
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ module Test
2
+ class Foo
3
+ include Import['test.dep']
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module Test
2
+ module Models
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Test
2
+ module Models
3
+ class Book
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Test
2
+ module Models
3
+ class User
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,2 @@
1
+ test:
2
+ bar: 'baz'
@@ -33,6 +33,7 @@ RSpec.configure do |config|
33
33
 
34
34
  config.before do
35
35
  @load_paths = $LOAD_PATH.dup
36
+ @loaded_features = $LOADED_FEATURES.dup
36
37
  Object.const_set(:Test, Module.new { |m| m.extend(TestNamespace) })
37
38
  end
38
39
 
@@ -40,6 +41,10 @@ RSpec.configure do |config|
40
41
  ($LOAD_PATH - @load_paths).each do |path|
41
42
  $LOAD_PATH.delete(path)
42
43
  end
44
+ ($LOADED_FEATURES - @loaded_features).each do |file|
45
+ $LOADED_FEATURES.delete(file)
46
+ end
47
+
43
48
  Test.remove_constants
44
49
  Object.send(:remove_const, :Test)
45
50
  end
@@ -4,6 +4,14 @@ RSpec.describe Dry::Component::Config do
4
4
  before do
5
5
  class Test::App < Dry::Component::Container
6
6
  configure do |config|
7
+ config.name = :application
8
+ config.root = SPEC_ROOT.join('fixtures/test').realpath
9
+ end
10
+ end
11
+
12
+ class Test::SubApp < Dry::Component::Container
13
+ configure do |config|
14
+ config.name = :subapp
7
15
  config.root = SPEC_ROOT.join('fixtures/test').realpath
8
16
  end
9
17
  end
@@ -12,4 +20,8 @@ RSpec.describe Dry::Component::Config do
12
20
  it 'loads config under component name' do
13
21
  expect(Test::App.options.foo).to eql('bar')
14
22
  end
23
+
24
+ it 'allows different components to have different configurations' do
25
+ expect(Test::SubApp.options.bar).to eql('baz')
26
+ end
15
27
  end
@@ -0,0 +1,50 @@
1
+ require 'dry/component/container'
2
+
3
+ RSpec.describe Dry::Component::Container, '.auto_register!' do
4
+ context 'with the standard loader' do
5
+ before do
6
+ class Test::Container < Dry::Component::Container
7
+ configure do |config|
8
+ config.root = SPEC_ROOT.join('fixtures').realpath
9
+ end
10
+
11
+ load_paths!('components')
12
+ auto_register!('components')
13
+ end
14
+ end
15
+
16
+ it { expect(Test::Container['foo']).to be_an_instance_of(Foo) }
17
+ it { expect(Test::Container['bar']).to be_an_instance_of(Bar) }
18
+ it { expect(Test::Container['bar.baz']).to be_an_instance_of(Bar::Baz) }
19
+ end
20
+
21
+ context 'with a custom loader' do
22
+ before do
23
+ class Test::Loader < Dry::Component::Loader
24
+ def identifier
25
+ super.gsub('.', '-')
26
+ end
27
+
28
+ def instance(*args)
29
+ constant.respond_to?(:call) ? constant : constant.new(*args)
30
+ end
31
+ end
32
+
33
+ class Test::Container < Dry::Component::Container
34
+ configure do |config|
35
+ config.root = SPEC_ROOT.join('fixtures').realpath
36
+ config.loader = ::Test::Loader
37
+ end
38
+
39
+ load_paths!('components')
40
+ auto_register!('components')
41
+ end
42
+ end
43
+
44
+ it { expect(Test::Container['foo']).to be_an_instance_of(Foo) }
45
+ it { expect(Test::Container['bar']).to eq(Bar) }
46
+ it { expect(Test::Container['bar'].call).to eq("Welcome to my Moe's Tavern!") }
47
+ it { expect(Test::Container['bar-baz']).to be_an_instance_of(Bar::Baz) }
48
+ end
49
+
50
+ end
@@ -0,0 +1,70 @@
1
+ require 'dry/component/container'
2
+
3
+ RSpec.describe Dry::Component::Container, '.import' do
4
+ subject(:app) { Class.new(Dry::Component::Container) }
5
+
6
+ let(:db) do
7
+ Class.new(Dry::Component::Container) do
8
+ register(:users, %w(jane joe))
9
+ end
10
+ end
11
+
12
+ shared_examples_for 'an extended container' do
13
+ it 'imports one container into another' do
14
+ expect(app.key?('persistence.users')).to be(false)
15
+
16
+ app.finalize!
17
+
18
+ expect(app['persistence.users']).to eql(%w(jane joe))
19
+ end
20
+ end
21
+
22
+ context 'when a container has a name' do
23
+ before do
24
+ db.configure { |c| c.name = :persistence }
25
+ app.import(db)
26
+ end
27
+
28
+ it_behaves_like 'an extended container'
29
+ end
30
+
31
+ context 'when container does not have a name' do
32
+ before do
33
+ app.import(persistence: db)
34
+ end
35
+
36
+ it_behaves_like 'an extended container'
37
+ end
38
+
39
+ describe 'import module' do
40
+ it 'loads component when it was not loaded in the imported container yet' do
41
+ class Test::Other < Dry::Component::Container
42
+ configure do |config|
43
+ config.root = SPEC_ROOT.join('fixtures/import_test').realpath
44
+ end
45
+
46
+ load_paths!('lib')
47
+ end
48
+
49
+ class Test::Container < Dry::Component::Container
50
+ configure do |config|
51
+ config.root = SPEC_ROOT.join('fixtures/test').realpath
52
+ end
53
+
54
+ load_paths!('lib')
55
+
56
+ import other: Test::Other
57
+ end
58
+
59
+ module Test
60
+ Import = Container::Inject
61
+ end
62
+
63
+ class Test::Foo
64
+ include Test::Import['other.test.bar']
65
+ end
66
+
67
+ expect(Test::Foo.new.bar).to be_instance_of(Test::Bar)
68
+ end
69
+ end
70
+ end
@@ -14,28 +14,28 @@ RSpec.describe Dry::Component::Container do
14
14
  end
15
15
 
16
16
  module Test
17
- Import = Container.import_module
17
+ Import = Container::Inject
18
18
  end
19
19
  end
20
20
 
21
21
  describe '.require' do
22
22
  it 'requires a single file' do
23
- container.require('lib/test/models')
23
+ container.require(Pathname('lib/test/models'))
24
24
 
25
25
  expect(Test.const_defined?(:Models)).to be(true)
26
26
  end
27
27
 
28
28
  it 'requires many files when glob pattern is passed' do
29
- container.require('lib/test/models/*.rb')
29
+ container.require(Pathname('lib/test/models/*.rb'))
30
30
 
31
31
  expect(Test::Models.const_defined?(:User)).to be(true)
32
32
  expect(Test::Models.const_defined?(:Book)).to be(true)
33
33
  end
34
34
  end
35
35
 
36
- describe '.require_component' do
37
- it 'requires components from configured load paths' do
38
- container.require_component('test.foo')
36
+ describe '.load_component' do
37
+ it 'loads and registers components from configured load paths' do
38
+ container.load_component('test.foo')
39
39
 
40
40
  expect(Test.const_defined?(:Foo)).to be(true)
41
41
  expect(Test.const_defined?(:Dep)).to be(true)
@@ -45,6 +45,25 @@ RSpec.describe Dry::Component::Container do
45
45
  end
46
46
  end
47
47
 
48
+ describe '.boot' do
49
+ before do
50
+ class Test::Container < Dry::Component::Container
51
+ configure do |config|
52
+ config.root = SPEC_ROOT.join('fixtures/lazytest').realpath
53
+ end
54
+
55
+ load_paths!('lib')
56
+ end
57
+ end
58
+
59
+ it 'lazy-boot a given component' do
60
+ container.boot(:bar)
61
+
62
+ expect(Test.const_defined?(:Bar)).to be(true)
63
+ expect(container.key?('test.bar')).to be(false)
64
+ end
65
+ end
66
+
48
67
  describe '.boot!' do
49
68
  shared_examples_for 'a booted component' do
50
69
  it 'boots a given component and finalizes it' do
@@ -98,5 +117,19 @@ RSpec.describe Dry::Component::Container do
98
117
  end
99
118
  end
100
119
  end
120
+
121
+ it 'passes container to the finalizer block' do
122
+ class Test::Container < Dry::Component::Container
123
+ configure { |c| c.env = :awesome }
124
+
125
+ finalize(:foo) do |container|
126
+ register(:w00t, container.config.env)
127
+ end
128
+ end
129
+
130
+ Test::Container.finalizers[:foo].()
131
+
132
+ expect(Test::Container[:w00t]).to be(:awesome)
133
+ end
101
134
  end
102
135
  end
@@ -0,0 +1,59 @@
1
+ RSpec.describe Dry::Component::Injector do
2
+ before do
3
+ class Test::Container < Dry::Component::Container
4
+ configure do |config|
5
+ config.root = SPEC_ROOT.join("fixtures/test").realpath
6
+ end
7
+
8
+ load_paths! "lib"
9
+ end
10
+ end
11
+
12
+ it "supports args injection by default" do
13
+ obj = Class.new do
14
+ include Test::Container::Inject["test.dep"]
15
+ end.new
16
+
17
+ expect(obj.dep).to be_a Test::Dep
18
+ end
19
+
20
+ it "supports args injection with explicit method" do
21
+ obj = Class.new do
22
+ include Test::Container::Inject.args["test.dep"]
23
+ end.new
24
+
25
+ expect(obj.dep).to be_a Test::Dep
26
+ end
27
+
28
+ it "supports hash injection" do
29
+ obj = Class.new do
30
+ include Test::Container::Inject.hash["test.dep"]
31
+ end.new
32
+
33
+ expect(obj.dep).to be_a Test::Dep
34
+ end
35
+
36
+ it "support kwargs injection" do
37
+ obj = Class.new do
38
+ include Test::Container::Inject.kwargs["test.dep"]
39
+ end.new
40
+
41
+ expect(obj.dep).to be_a Test::Dep
42
+ end
43
+
44
+ it "allows injection strategies to be swapped" do
45
+ obj = Class.new do
46
+ include Test::Container::Inject.kwargs.hash["test.dep"]
47
+ end.new
48
+
49
+ expect(obj.dep).to be_a Test::Dep
50
+ end
51
+
52
+ it "supports aliases" do
53
+ obj = Class.new do
54
+ include Test::Container::Inject[foo: "test.dep"]
55
+ end.new
56
+
57
+ expect(obj.foo).to be_a Test::Dep
58
+ end
59
+ end
@@ -9,12 +9,6 @@ RSpec.describe Dry::Component::Loader do
9
9
  end
10
10
 
11
11
  shared_examples_for 'a valid component' do
12
- describe '#name' do
13
- it 'returns name of the constant' do
14
- expect(component.name).to eql('Test::Bar')
15
- end
16
- end
17
-
18
12
  describe '#constant' do
19
13
  it 'returns the constant' do
20
14
  expect(component.constant).to be(Test::Bar)
@@ -47,19 +41,19 @@ RSpec.describe Dry::Component::Loader do
47
41
  end
48
42
 
49
43
  context 'from identifier as a symbol' do
50
- subject(:component) { Dry::Component::Loader(:'test.bar') }
44
+ subject(:component) { Dry::Component::Loader.new(:'test.bar') }
51
45
 
52
46
  it_behaves_like 'a valid component'
53
47
  end
54
48
 
55
49
  context 'from identifier as a string' do
56
- subject(:component) { Dry::Component::Loader('test.bar') }
50
+ subject(:component) { Dry::Component::Loader.new('test.bar') }
57
51
 
58
52
  it_behaves_like 'a valid component'
59
53
  end
60
54
 
61
55
  context 'from path' do
62
- subject(:component) { Dry::Component::Loader('test/bar') }
56
+ subject(:component) { Dry::Component::Loader.new('test/bar') }
63
57
 
64
58
  it_behaves_like 'a valid component'
65
59
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-component
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-24 00:00:00.000000000 Z
11
+ date: 2016-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: memoizable
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '0.4'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '0.4'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: inflecto
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -44,34 +30,34 @@ dependencies:
44
30
  requirements:
45
31
  - - "~>"
46
32
  - !ruby/object:Gem::Version
47
- version: '0.2'
33
+ version: '0.3'
48
34
  - - ">="
49
35
  - !ruby/object:Gem::Version
50
- version: 0.2.7
36
+ version: 0.3.4
51
37
  type: :runtime
52
38
  prerelease: false
53
39
  version_requirements: !ruby/object:Gem::Requirement
54
40
  requirements:
55
41
  - - "~>"
56
42
  - !ruby/object:Gem::Version
57
- version: '0.2'
43
+ version: '0.3'
58
44
  - - ">="
59
45
  - !ruby/object:Gem::Version
60
- version: 0.2.7
46
+ version: 0.3.4
61
47
  - !ruby/object:Gem::Dependency
62
48
  name: dry-auto_inject
63
49
  requirement: !ruby/object:Gem::Requirement
64
50
  requirements:
65
51
  - - "~>"
66
52
  - !ruby/object:Gem::Version
67
- version: '0.1'
53
+ version: '0.3'
68
54
  type: :runtime
69
55
  prerelease: false
70
56
  version_requirements: !ruby/object:Gem::Requirement
71
57
  requirements:
72
58
  - - "~>"
73
59
  - !ruby/object:Gem::Version
74
- version: '0.1'
60
+ version: '0.3'
75
61
  - !ruby/object:Gem::Dependency
76
62
  name: dry-configurable
77
63
  requirement: !ruby/object:Gem::Requirement
@@ -150,8 +136,23 @@ files:
150
136
  - lib/dry/component.rb
151
137
  - lib/dry/component/config.rb
152
138
  - lib/dry/component/container.rb
139
+ - lib/dry/component/injector.rb
153
140
  - lib/dry/component/loader.rb
154
141
  - lib/dry/component/version.rb
142
+ - spec/fixtures/components/bar.rb
143
+ - spec/fixtures/components/bar/baz.rb
144
+ - spec/fixtures/components/foo.rb
145
+ - spec/fixtures/import_test/config/application.yml
146
+ - spec/fixtures/import_test/core/boot/bar.rb
147
+ - spec/fixtures/import_test/lib/test/bar.rb
148
+ - spec/fixtures/import_test/lib/test/foo.rb
149
+ - spec/fixtures/lazytest/config/application.yml
150
+ - spec/fixtures/lazytest/core/boot/bar.rb
151
+ - spec/fixtures/lazytest/lib/test/dep.rb
152
+ - spec/fixtures/lazytest/lib/test/foo.rb
153
+ - spec/fixtures/lazytest/lib/test/models.rb
154
+ - spec/fixtures/lazytest/lib/test/models/book.rb
155
+ - spec/fixtures/lazytest/lib/test/models/user.rb
155
156
  - spec/fixtures/other/config/boot/bar.rb
156
157
  - spec/fixtures/other/lib/test/dep.rb
157
158
  - spec/fixtures/other/lib/test/foo.rb
@@ -159,6 +160,7 @@ files:
159
160
  - spec/fixtures/other/lib/test/models/book.rb
160
161
  - spec/fixtures/other/lib/test/models/user.rb
161
162
  - spec/fixtures/test/config/application.yml
163
+ - spec/fixtures/test/config/subapp.yml
162
164
  - spec/fixtures/test/core/boot/bar.rb
163
165
  - spec/fixtures/test/lib/test/dep.rb
164
166
  - spec/fixtures/test/lib/test/foo.rb
@@ -167,7 +169,10 @@ files:
167
169
  - spec/fixtures/test/lib/test/models/user.rb
168
170
  - spec/spec_helper.rb
169
171
  - spec/unit/config_spec.rb
172
+ - spec/unit/container/auto_register_spec.rb
173
+ - spec/unit/container/import_spec.rb
170
174
  - spec/unit/container_spec.rb
175
+ - spec/unit/injector_spec.rb
171
176
  - spec/unit/loader_spec.rb
172
177
  homepage: https://github.com/dryrb/dry-component
173
178
  licenses:
@@ -189,11 +194,25 @@ required_rubygems_version: !ruby/object:Gem::Requirement
189
194
  version: '0'
190
195
  requirements: []
191
196
  rubyforge_project:
192
- rubygems_version: 2.4.5.1
197
+ rubygems_version: 2.5.1
193
198
  signing_key:
194
199
  specification_version: 4
195
200
  summary: Organize your code into reusable components
196
201
  test_files:
202
+ - spec/fixtures/components/bar.rb
203
+ - spec/fixtures/components/bar/baz.rb
204
+ - spec/fixtures/components/foo.rb
205
+ - spec/fixtures/import_test/config/application.yml
206
+ - spec/fixtures/import_test/core/boot/bar.rb
207
+ - spec/fixtures/import_test/lib/test/bar.rb
208
+ - spec/fixtures/import_test/lib/test/foo.rb
209
+ - spec/fixtures/lazytest/config/application.yml
210
+ - spec/fixtures/lazytest/core/boot/bar.rb
211
+ - spec/fixtures/lazytest/lib/test/dep.rb
212
+ - spec/fixtures/lazytest/lib/test/foo.rb
213
+ - spec/fixtures/lazytest/lib/test/models.rb
214
+ - spec/fixtures/lazytest/lib/test/models/book.rb
215
+ - spec/fixtures/lazytest/lib/test/models/user.rb
197
216
  - spec/fixtures/other/config/boot/bar.rb
198
217
  - spec/fixtures/other/lib/test/dep.rb
199
218
  - spec/fixtures/other/lib/test/foo.rb
@@ -201,6 +220,7 @@ test_files:
201
220
  - spec/fixtures/other/lib/test/models/book.rb
202
221
  - spec/fixtures/other/lib/test/models/user.rb
203
222
  - spec/fixtures/test/config/application.yml
223
+ - spec/fixtures/test/config/subapp.yml
204
224
  - spec/fixtures/test/core/boot/bar.rb
205
225
  - spec/fixtures/test/lib/test/dep.rb
206
226
  - spec/fixtures/test/lib/test/foo.rb
@@ -209,5 +229,8 @@ test_files:
209
229
  - spec/fixtures/test/lib/test/models/user.rb
210
230
  - spec/spec_helper.rb
211
231
  - spec/unit/config_spec.rb
232
+ - spec/unit/container/auto_register_spec.rb
233
+ - spec/unit/container/import_spec.rb
212
234
  - spec/unit/container_spec.rb
235
+ - spec/unit/injector_spec.rb
213
236
  - spec/unit/loader_spec.rb