dry-component 0.0.1 → 0.1.0

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
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