dry-component 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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