dry-component 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ee78e672954f38457a8469c522b169caff7433ae
4
+ data.tar.gz: 8b649eed1db04abef81ac6d0512f38bbafd26cb3
5
+ SHA512:
6
+ metadata.gz: a0bf880fd5c645e2fa85c40061f66a9a8da31be70f0fadff877b15cccede26560215b52895fa9e1a443609f8db9dc587b00c821e1d75d0768675f98d28d978cd
7
+ data.tar.gz: 4ac3f8c0f089975e808736577bfb0e8fc3fcafebfeb6ca8ed440d4a060d9c2ef8eaadfc8801c3609e6991926bbd08fafc8fd4a9ce5aa4a55232f626f33a00843
@@ -0,0 +1,36 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /vendor/bundle
26
+ /lib/bundler/man/
27
+
28
+ # for a library or gem, you might want to ignore these files since the code is
29
+ # intended to run in multiple environments; otherwise, check them in:
30
+ # Gemfile.lock
31
+ # .ruby-version
32
+ # .ruby-gemset
33
+
34
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
35
+ .rvmrc
36
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require ./spec/spec_helper
3
+ --order random
@@ -0,0 +1,34 @@
1
+ # Generated by `rubocop --auto-gen-config`
2
+ inherit_from: .rubocop_todo.yml
3
+
4
+ # No need to handle LoadError in spec helper
5
+ Lint/HandleExceptions:
6
+ Exclude:
7
+ - spec/spec_helper.rb
8
+
9
+ Style/FileName:
10
+ Exclude:
11
+ - 'lib/dry-component.rb'
12
+
13
+ # Documentation checked by Inch CI
14
+ Style/Documentation:
15
+ Enabled: false
16
+
17
+ # Ain't nobody got time for that!
18
+ Style/MethodCallParentheses:
19
+ Enabled: false
20
+
21
+ Style/Lambda:
22
+ Enabled: false
23
+
24
+ Style/BlockDelimiters:
25
+ Enabled: false
26
+
27
+ Style/LambdaCall:
28
+ EnforcedStyle: braces
29
+
30
+ Style/MultilineBlockChain:
31
+ Enabled: false
32
+
33
+ Style/StabbyLambdaParentheses:
34
+ EnforcedStyle: require_no_parentheses
@@ -0,0 +1,26 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2015-12-23 21:42:26 +0000 using RuboCop version 0.35.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ Metrics/AbcSize:
11
+ Max: 25
12
+
13
+ # Offense count: 1
14
+ # Configuration parameters: CountComments.
15
+ Metrics/ClassLength:
16
+ Max: 130
17
+
18
+ # Offense count: 1
19
+ # Configuration parameters: AllowURI, URISchemes.
20
+ Metrics/LineLength:
21
+ Max: 84
22
+
23
+ # Offense count: 2
24
+ # Configuration parameters: CountComments.
25
+ Metrics/MethodLength:
26
+ Max: 14
@@ -0,0 +1,26 @@
1
+ language: ruby
2
+ sudo: false
3
+ cache: bundler
4
+ bundler_args: --without tools
5
+ script:
6
+ - bundle exec rake spec
7
+ rvm:
8
+ - 2.0
9
+ - 2.1
10
+ - 2.2
11
+ - rbx-2
12
+ - jruby-9000
13
+ - ruby-head
14
+ - jruby-head
15
+ matrix:
16
+ allow_failures:
17
+ - rvm: ruby-head
18
+ - rvm: jruby-head
19
+ notifications:
20
+ email: false
21
+ webhooks:
22
+ urls:
23
+ - https://webhooks.gitter.im/e/19098b4253a72c9796db
24
+ on_success: change # options: [always|never|change] default: always
25
+ on_failure: always # options: [always|never|change] default: always
26
+ on_start: false # default: false
@@ -0,0 +1,3 @@
1
+ # v0.0.1 2015-12-24
2
+
3
+ First public release, extracted from rodakase project
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'codeclimate-test-reporter', platforms: :rbx
6
+
7
+ group :tools do
8
+ gem 'byebug', platforms: :mri
9
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Dryrb Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,218 @@
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>
2
+
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>
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.
12
+
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.
215
+
216
+ ## LICENSE
217
+
218
+ See `LICENSE` file.
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
5
+
6
+ require 'rspec/core'
7
+ require 'rspec/core/rake_task'
8
+
9
+ task default: :spec
10
+
11
+ desc 'Run all specs in spec directory'
12
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ require File.expand_path('../lib/dry/component/version', __FILE__)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'dry-component'
6
+ spec.version = Dry::Component::VERSION
7
+ spec.authors = ['Piotr Solnica']
8
+ spec.email = ['piotr.solnica@gmail.com']
9
+ spec.summary = 'Organize your code into reusable components'
10
+ spec.homepage = 'https://github.com/dryrb/dry-component'
11
+ spec.license = 'MIT'
12
+
13
+ spec.files = `git ls-files -z`.split("\x0")
14
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
15
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
+ spec.require_paths = ['lib']
17
+
18
+ spec.add_runtime_dependency 'memoizable', '~> 0.4'
19
+ 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'
22
+ spec.add_runtime_dependency 'dry-configurable', '~> 0.1'
23
+
24
+ spec.add_development_dependency 'bundler'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'rspec'
27
+ end
@@ -0,0 +1 @@
1
+ require 'dry/component'
@@ -0,0 +1,4 @@
1
+ module Dry
2
+ module Component
3
+ end
4
+ end
@@ -0,0 +1,23 @@
1
+ require 'yaml'
2
+
3
+ module Dry
4
+ module Component
5
+ class Config
6
+ extend Dry::Configurable
7
+
8
+ def self.load(root, env)
9
+ path = root.join('config').join('application.yml')
10
+
11
+ return {} unless File.exist?(path)
12
+
13
+ yaml = YAML.load_file(path)
14
+
15
+ yaml.fetch(env.to_s).each do |key, value|
16
+ setting key.downcase.to_sym, ENV.fetch(key, value)
17
+ end
18
+
19
+ config
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,181 @@
1
+ require 'inflecto'
2
+ require 'dry-container'
3
+ require 'dry-auto_inject'
4
+
5
+ require 'dry/component/loader'
6
+ require 'dry/component/config'
7
+
8
+ module Dry
9
+ module Component
10
+ class Container
11
+ extend Dry::Container::Mixin
12
+
13
+ setting :env
14
+ setting :root, Pathname.pwd.freeze
15
+ setting :core_dir, 'core'.freeze
16
+ setting :auto_register
17
+ setting :options
18
+
19
+ def self.configure(env = config.env, &block)
20
+ return self if configured?
21
+
22
+ super() do |config|
23
+ yield(config) if block
24
+ config.options = Config.load(root, env)
25
+ end
26
+
27
+ load_paths!(config.core_dir)
28
+
29
+ @configured = true
30
+
31
+ self
32
+ end
33
+
34
+ def self.options
35
+ config.options
36
+ end
37
+
38
+ def self.finalize(name, &block)
39
+ finalizers[name] = block
40
+ end
41
+
42
+ def self.configured?
43
+ @configured
44
+ end
45
+
46
+ def self.finalize!(&_block)
47
+ yield(self) if block_given?
48
+
49
+ Dir[root.join("#{config.core_dir}/boot/**/*.rb")].each do |path|
50
+ boot!(File.basename(path, '.rb').to_sym)
51
+ end
52
+
53
+ auto_register.each(&method(:auto_register!)) if auto_register?
54
+
55
+ freeze
56
+ end
57
+
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
+ }
65
+ end
66
+
67
+ def self.auto_register!(dir, &_block)
68
+ dir_root = root.join(dir.to_s.split('/')[0])
69
+
70
+ Dir["#{root}/#{dir}/**/*.rb"].each do |path|
71
+ component_path = path.to_s.gsub("#{dir_root}/", '').gsub('.rb', '')
72
+ Component.Loader(component_path).tap do |component|
73
+ next if key?(component.identifier)
74
+
75
+ Kernel.require component.path
76
+
77
+ if block_given?
78
+ register(component.identifier, yield(component.constant))
79
+ else
80
+ register(component.identifier) { component.instance }
81
+ end
82
+ end
83
+ end
84
+
85
+ self
86
+ end
87
+
88
+ def self.boot!(name)
89
+ check_component_identifier!(name)
90
+ return self unless booted?(name)
91
+ boot(name)
92
+ self
93
+ end
94
+
95
+ def self.boot(name)
96
+ require "#{config.core_dir}/boot/#{name}.rb"
97
+
98
+ finalizers[name].tap do |finalizer|
99
+ finalizer.() if finalizer
100
+ end
101
+
102
+ booted[name] = true
103
+ end
104
+
105
+ def self.booted?(name)
106
+ !booted.key?(name)
107
+ end
108
+
109
+ def self.require(*paths)
110
+ paths.flat_map { |path|
111
+ path.include?('*') ? Dir[root.join(path)] : root.join(path)
112
+ }.each { |path|
113
+ Kernel.require path.to_s
114
+ }
115
+ end
116
+
117
+ def self.load_component(key)
118
+ require_component(key) { |klass| register(key) { klass.new } }
119
+ end
120
+
121
+ def self.require_component(key, &block)
122
+ component = Component.Loader(key)
123
+ path = load_paths.detect { |p| p.join(component.file).exist? }
124
+
125
+ if path
126
+ Kernel.require component.path
127
+ yield(component.constant) if block
128
+ else
129
+ fail ArgumentError, "could not resolve require file for #{key}"
130
+ end
131
+ end
132
+
133
+ def self.root
134
+ config.root
135
+ end
136
+
137
+ def self.load_paths!(*dirs)
138
+ dirs.map(&:to_s).each do |dir|
139
+ path = root.join(dir)
140
+ load_paths << path
141
+ $LOAD_PATH.unshift(path.to_s)
142
+ end
143
+ self
144
+ end
145
+
146
+ def self.load_paths
147
+ @load_paths ||= []
148
+ end
149
+
150
+ def self.booted
151
+ @booted ||= {}
152
+ end
153
+
154
+ def self.finalizers
155
+ @finalizers ||= {}
156
+ end
157
+
158
+ private
159
+
160
+ def self.auto_register
161
+ Array(config.auto_register)
162
+ end
163
+
164
+ def self.auto_register?
165
+ !auto_register.empty?
166
+ end
167
+
168
+ def self.check_component_identifier!(name)
169
+ fail(
170
+ ArgumentError,
171
+ 'component identifier must be a symbol'
172
+ ) unless name.is_a?(Symbol)
173
+
174
+ fail(
175
+ ArgumentError,
176
+ "component identifier +#{name}+ is invalid or boot file is missing"
177
+ ) unless root.join("#{config.core_dir}/boot/#{name}.rb").exist?
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,47 @@
1
+ require 'memoizable'
2
+ require 'inflecto'
3
+
4
+ module Dry
5
+ module Component
6
+ def self.Loader(input)
7
+ Loader.new(Loader.identifier(input), Loader.path(input))
8
+ end
9
+
10
+ class Loader
11
+ include Memoizable
12
+
13
+ IDENTIFIER_SEPARATOR = '.'.freeze
14
+ PATH_SEPARATOR = '/'.freeze
15
+
16
+ attr_reader :identifier, :path, :file
17
+
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
29
+ @file = "#{path}.rb"
30
+ end
31
+
32
+ def name
33
+ Inflecto.camelize(path)
34
+ end
35
+ memoize :name
36
+
37
+ def constant
38
+ Inflecto.constantize(name)
39
+ end
40
+ memoize :constant
41
+
42
+ def instance(*args)
43
+ constant.new(*args)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,5 @@
1
+ module Dry
2
+ module Component
3
+ VERSION = '0.0.1'.freeze
4
+ end
5
+ end
@@ -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
+ 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,46 @@
1
+ # encoding: utf-8
2
+
3
+ if RUBY_ENGINE == 'rbx'
4
+ require 'codeclimate-test-reporter'
5
+ CodeClimate::TestReporter.start
6
+ end
7
+
8
+ begin
9
+ require 'byebug'
10
+ rescue LoadError; end
11
+
12
+ SPEC_ROOT = Pathname(__FILE__).dirname
13
+
14
+ Dir[SPEC_ROOT.join('support/*.rb').to_s].each { |f| require f }
15
+ Dir[SPEC_ROOT.join('shared/*.rb').to_s].each { |f| require f }
16
+
17
+ require 'dry/component/container'
18
+
19
+ class Dry::Component::Container
20
+ setting :env, 'test'
21
+ end
22
+
23
+ module TestNamespace
24
+ def remove_constants
25
+ constants.each do |name|
26
+ remove_const(name)
27
+ end
28
+ end
29
+ end
30
+
31
+ RSpec.configure do |config|
32
+ config.disable_monkey_patching!
33
+
34
+ config.before do
35
+ @load_paths = $LOAD_PATH.dup
36
+ Object.const_set(:Test, Module.new { |m| m.extend(TestNamespace) })
37
+ end
38
+
39
+ config.after do
40
+ ($LOAD_PATH - @load_paths).each do |path|
41
+ $LOAD_PATH.delete(path)
42
+ end
43
+ Test.remove_constants
44
+ Object.send(:remove_const, :Test)
45
+ end
46
+ end
@@ -0,0 +1,15 @@
1
+ require 'dry/component/container'
2
+
3
+ RSpec.describe Dry::Component::Config do
4
+ before do
5
+ class Test::App < Dry::Component::Container
6
+ configure do |config|
7
+ config.root = SPEC_ROOT.join('fixtures/test').realpath
8
+ end
9
+ end
10
+ end
11
+
12
+ it 'loads config under component name' do
13
+ expect(Test::App.options.foo).to eql('bar')
14
+ end
15
+ end
@@ -0,0 +1,102 @@
1
+ require 'dry/component/container'
2
+
3
+ RSpec.describe Dry::Component::Container do
4
+ subject(:container) { Test::Container }
5
+
6
+ context 'with default core dir' do
7
+ before do
8
+ class Test::Container < Dry::Component::Container
9
+ configure do |config|
10
+ config.root = SPEC_ROOT.join('fixtures/test').realpath
11
+ end
12
+
13
+ load_paths!('lib')
14
+ end
15
+
16
+ module Test
17
+ Import = Container.import_module
18
+ end
19
+ end
20
+
21
+ describe '.require' do
22
+ it 'requires a single file' do
23
+ container.require('lib/test/models')
24
+
25
+ expect(Test.const_defined?(:Models)).to be(true)
26
+ end
27
+
28
+ it 'requires many files when glob pattern is passed' do
29
+ container.require('lib/test/models/*.rb')
30
+
31
+ expect(Test::Models.const_defined?(:User)).to be(true)
32
+ expect(Test::Models.const_defined?(:Book)).to be(true)
33
+ end
34
+ end
35
+
36
+ describe '.require_component' do
37
+ it 'requires components from configured load paths' do
38
+ container.require_component('test.foo')
39
+
40
+ expect(Test.const_defined?(:Foo)).to be(true)
41
+ expect(Test.const_defined?(:Dep)).to be(true)
42
+
43
+ expect(Test::Foo.new.dep).to be_instance_of(Test::Dep)
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '.boot!' do
49
+ shared_examples_for 'a booted component' do
50
+ it 'boots a given component and finalizes it' do
51
+ container.boot!(:bar)
52
+
53
+ expect(Test.const_defined?(:Bar)).to be(true)
54
+ expect(container['test.bar']).to eql('I was finalized')
55
+ end
56
+
57
+ it 'expects a symbol identifier matching file name' do
58
+ expect {
59
+ container.boot!('bar')
60
+ }.to raise_error(ArgumentError, 'component identifier must be a symbol')
61
+ end
62
+
63
+ it 'expects identifier to point to an existing boot file' do
64
+ expect {
65
+ container.boot!(:foo)
66
+ }.to raise_error(
67
+ ArgumentError,
68
+ 'component identifier +foo+ is invalid or boot file is missing'
69
+ )
70
+ end
71
+ end
72
+
73
+ context 'with the default core dir' do
74
+ it_behaves_like 'a booted component' do
75
+ before do
76
+ class Test::Container < Dry::Component::Container
77
+ configure do |config|
78
+ config.root = SPEC_ROOT.join('fixtures/test').realpath
79
+ end
80
+
81
+ load_paths!('lib')
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ context 'with a custom core dir' do
88
+ it_behaves_like 'a booted component' do
89
+ before do
90
+ class Test::Container < Dry::Component::Container
91
+ configure do |config|
92
+ config.root = SPEC_ROOT.join('fixtures/other').realpath
93
+ config.core_dir = 'config'
94
+ end
95
+
96
+ load_paths!('lib')
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,66 @@
1
+ require 'dry/component/loader'
2
+
3
+ RSpec.describe Dry::Component::Loader do
4
+ before do
5
+ module Test
6
+ class Bar
7
+ end
8
+ end
9
+ end
10
+
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
+ describe '#constant' do
19
+ it 'returns the constant' do
20
+ expect(component.constant).to be(Test::Bar)
21
+ end
22
+ end
23
+
24
+ describe '#identifier' do
25
+ it 'returns container identifier' do
26
+ expect(component.identifier).to eql('test.bar')
27
+ end
28
+ end
29
+
30
+ describe '#path' do
31
+ it 'returns relative path to file defining the component constant' do
32
+ expect(component.path).to eql('test/bar')
33
+ end
34
+ end
35
+
36
+ describe '#file' do
37
+ it 'returns relative path to file with ext defining the component constant' do
38
+ expect(component.file).to eql('test/bar.rb')
39
+ end
40
+ end
41
+
42
+ describe '#instance' do
43
+ it 'builds a component class instance' do
44
+ expect(component.instance).to be_instance_of(Test::Bar)
45
+ end
46
+ end
47
+ end
48
+
49
+ context 'from identifier as a symbol' do
50
+ subject(:component) { Dry::Component::Loader(:'test.bar') }
51
+
52
+ it_behaves_like 'a valid component'
53
+ end
54
+
55
+ context 'from identifier as a string' do
56
+ subject(:component) { Dry::Component::Loader('test.bar') }
57
+
58
+ it_behaves_like 'a valid component'
59
+ end
60
+
61
+ context 'from path' do
62
+ subject(:component) { Dry::Component::Loader('test/bar') }
63
+
64
+ it_behaves_like 'a valid component'
65
+ end
66
+ end
metadata ADDED
@@ -0,0 +1,213 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dry-component
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Piotr Solnica
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-12-24 00:00:00.000000000 Z
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
+ - !ruby/object:Gem::Dependency
28
+ name: inflecto
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-container
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.2'
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 0.2.7
51
+ type: :runtime
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: '0.2'
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 0.2.7
61
+ - !ruby/object:Gem::Dependency
62
+ name: dry-auto_inject
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.1'
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.1'
75
+ - !ruby/object:Gem::Dependency
76
+ name: dry-configurable
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.1'
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.1'
89
+ - !ruby/object:Gem::Dependency
90
+ name: bundler
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: rake
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: rspec
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ description:
132
+ email:
133
+ - piotr.solnica@gmail.com
134
+ executables: []
135
+ extensions: []
136
+ extra_rdoc_files: []
137
+ files:
138
+ - ".gitignore"
139
+ - ".rspec"
140
+ - ".rubocop.yml"
141
+ - ".rubocop_todo.yml"
142
+ - ".travis.yml"
143
+ - CHANGELOG.md
144
+ - Gemfile
145
+ - LICENSE
146
+ - README.md
147
+ - Rakefile
148
+ - dry-component.gemspec
149
+ - lib/dry-component.rb
150
+ - lib/dry/component.rb
151
+ - lib/dry/component/config.rb
152
+ - lib/dry/component/container.rb
153
+ - lib/dry/component/loader.rb
154
+ - lib/dry/component/version.rb
155
+ - spec/fixtures/other/config/boot/bar.rb
156
+ - spec/fixtures/other/lib/test/dep.rb
157
+ - spec/fixtures/other/lib/test/foo.rb
158
+ - spec/fixtures/other/lib/test/models.rb
159
+ - spec/fixtures/other/lib/test/models/book.rb
160
+ - spec/fixtures/other/lib/test/models/user.rb
161
+ - spec/fixtures/test/config/application.yml
162
+ - spec/fixtures/test/core/boot/bar.rb
163
+ - spec/fixtures/test/lib/test/dep.rb
164
+ - spec/fixtures/test/lib/test/foo.rb
165
+ - spec/fixtures/test/lib/test/models.rb
166
+ - spec/fixtures/test/lib/test/models/book.rb
167
+ - spec/fixtures/test/lib/test/models/user.rb
168
+ - spec/spec_helper.rb
169
+ - spec/unit/config_spec.rb
170
+ - spec/unit/container_spec.rb
171
+ - spec/unit/loader_spec.rb
172
+ homepage: https://github.com/dryrb/dry-component
173
+ licenses:
174
+ - MIT
175
+ metadata: {}
176
+ post_install_message:
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubyforge_project:
192
+ rubygems_version: 2.4.5.1
193
+ signing_key:
194
+ specification_version: 4
195
+ summary: Organize your code into reusable components
196
+ test_files:
197
+ - spec/fixtures/other/config/boot/bar.rb
198
+ - spec/fixtures/other/lib/test/dep.rb
199
+ - spec/fixtures/other/lib/test/foo.rb
200
+ - spec/fixtures/other/lib/test/models.rb
201
+ - spec/fixtures/other/lib/test/models/book.rb
202
+ - spec/fixtures/other/lib/test/models/user.rb
203
+ - spec/fixtures/test/config/application.yml
204
+ - spec/fixtures/test/core/boot/bar.rb
205
+ - spec/fixtures/test/lib/test/dep.rb
206
+ - spec/fixtures/test/lib/test/foo.rb
207
+ - spec/fixtures/test/lib/test/models.rb
208
+ - spec/fixtures/test/lib/test/models/book.rb
209
+ - spec/fixtures/test/lib/test/models/user.rb
210
+ - spec/spec_helper.rb
211
+ - spec/unit/config_spec.rb
212
+ - spec/unit/container_spec.rb
213
+ - spec/unit/loader_spec.rb