dry-system 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +39 -0
- data/.rspec +3 -0
- data/.rubocop.yml +34 -0
- data/.rubocop_todo.yml +26 -0
- data/.travis.yml +26 -0
- data/.yardopts +5 -0
- data/CHANGELOG.md +158 -0
- data/Gemfile +9 -0
- data/LICENSE +20 -0
- data/README.md +23 -0
- data/Rakefile +12 -0
- data/dry-system.gemspec +30 -0
- data/examples/standalone/Gemfile +5 -0
- data/examples/standalone/lib/user_repo.rb +5 -0
- data/examples/standalone/run.rb +7 -0
- data/examples/standalone/system/boot/persistence.rb +13 -0
- data/examples/standalone/system/container.rb +9 -0
- data/examples/standalone/system/import.rb +3 -0
- data/lib/dry-system.rb +1 -0
- data/lib/dry/system.rb +4 -0
- data/lib/dry/system/auto_registrar.rb +80 -0
- data/lib/dry/system/booter.rb +101 -0
- data/lib/dry/system/component.rb +167 -0
- data/lib/dry/system/constants.rb +9 -0
- data/lib/dry/system/container.rb +500 -0
- data/lib/dry/system/errors.rb +62 -0
- data/lib/dry/system/importer.rb +53 -0
- data/lib/dry/system/injector.rb +68 -0
- data/lib/dry/system/lifecycle.rb +104 -0
- data/lib/dry/system/loader.rb +69 -0
- data/lib/dry/system/version.rb +5 -0
- data/spec/fixtures/components/bar.rb +5 -0
- data/spec/fixtures/components/bar/baz.rb +4 -0
- data/spec/fixtures/components/foo.rb +2 -0
- data/spec/fixtures/import_test/config/application.yml +2 -0
- data/spec/fixtures/import_test/lib/test/bar.rb +4 -0
- data/spec/fixtures/import_test/lib/test/foo.rb +5 -0
- data/spec/fixtures/import_test/system/boot/bar.rb +11 -0
- data/spec/fixtures/lazytest/config/application.yml +2 -0
- data/spec/fixtures/lazytest/lib/test/dep.rb +4 -0
- data/spec/fixtures/lazytest/lib/test/foo.rb +5 -0
- data/spec/fixtures/lazytest/lib/test/models.rb +4 -0
- data/spec/fixtures/lazytest/lib/test/models/book.rb +6 -0
- data/spec/fixtures/lazytest/lib/test/models/user.rb +6 -0
- data/spec/fixtures/lazytest/system/boot/bar.rb +15 -0
- data/spec/fixtures/namespaced_components/namespaced/bar.rb +5 -0
- data/spec/fixtures/namespaced_components/namespaced/foo.rb +4 -0
- data/spec/fixtures/other/config/boot/bar.rb +11 -0
- data/spec/fixtures/other/lib/test/dep.rb +4 -0
- data/spec/fixtures/other/lib/test/foo.rb +5 -0
- data/spec/fixtures/other/lib/test/models.rb +4 -0
- data/spec/fixtures/other/lib/test/models/book.rb +6 -0
- data/spec/fixtures/other/lib/test/models/user.rb +6 -0
- data/spec/fixtures/test/config/application.yml +2 -0
- data/spec/fixtures/test/config/subapp.yml +2 -0
- data/spec/fixtures/test/lib/test/dep.rb +4 -0
- data/spec/fixtures/test/lib/test/foo.rb +5 -0
- data/spec/fixtures/test/lib/test/models.rb +4 -0
- data/spec/fixtures/test/lib/test/models/book.rb +6 -0
- data/spec/fixtures/test/lib/test/models/user.rb +6 -0
- data/spec/fixtures/test/lib/test/singleton_dep.rb +7 -0
- data/spec/fixtures/test/log/.gitkeep +0 -0
- data/spec/fixtures/test/system/boot/bar.rb +11 -0
- data/spec/fixtures/test/system/boot/client.rb +7 -0
- data/spec/fixtures/test/system/boot/db.rb +1 -0
- data/spec/fixtures/test/system/boot/logger.rb +5 -0
- data/spec/fixtures/umbrella/system/boot/db.rb +10 -0
- data/spec/integration/boot_spec.rb +18 -0
- data/spec/integration/import_spec.rb +63 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/unit/component_spec.rb +116 -0
- data/spec/unit/container/auto_register_spec.rb +85 -0
- data/spec/unit/container/finalize_spec.rb +85 -0
- data/spec/unit/container/import_spec.rb +70 -0
- data/spec/unit/container/injector_spec.rb +29 -0
- data/spec/unit/container_spec.rb +165 -0
- data/spec/unit/injector_spec.rb +72 -0
- data/spec/unit/loader_spec.rb +64 -0
- metadata +295 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
RSpec.describe Dry::System::Container, '.finalize' do
|
2
|
+
subject(:system) { Test::App }
|
3
|
+
|
4
|
+
let(:db) { spy(:db) }
|
5
|
+
|
6
|
+
before do
|
7
|
+
Test.const_set(:DB, db)
|
8
|
+
|
9
|
+
module Test
|
10
|
+
class App < Dry::System::Container
|
11
|
+
configure do |config|
|
12
|
+
config.root = SPEC_ROOT.join('fixtures/test')
|
13
|
+
end
|
14
|
+
|
15
|
+
finalize(:db) do
|
16
|
+
register(:db, Test::DB)
|
17
|
+
|
18
|
+
init do
|
19
|
+
db.establish_connection
|
20
|
+
end
|
21
|
+
|
22
|
+
start do
|
23
|
+
db.load
|
24
|
+
end
|
25
|
+
|
26
|
+
stop do
|
27
|
+
db.close_connection
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#init' do
|
35
|
+
it 'calls init function' do
|
36
|
+
system.booter.(:db).init
|
37
|
+
expect(db).to have_received(:establish_connection)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#start' do
|
42
|
+
it 'calls start function' do
|
43
|
+
system.booter.(:db).start
|
44
|
+
expect(db).to have_received(:load)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#stop' do
|
49
|
+
it 'calls stop function' do
|
50
|
+
system.booter.(:db).stop
|
51
|
+
expect(db).to have_received(:close_connection)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
specify 'boot triggers init' do
|
56
|
+
system.booter.boot(:db)
|
57
|
+
|
58
|
+
expect(db).to have_received(:establish_connection)
|
59
|
+
expect(db).to_not have_received(:load)
|
60
|
+
end
|
61
|
+
|
62
|
+
specify 'boot! triggers init + start' do
|
63
|
+
system.booter.boot!(:db)
|
64
|
+
|
65
|
+
expect(db).to have_received(:establish_connection)
|
66
|
+
expect(db).to have_received(:load)
|
67
|
+
end
|
68
|
+
|
69
|
+
specify 'booter returns cached lifecycle objects' do
|
70
|
+
expect(system.booter.(:db)).to be(system.booter.(:db))
|
71
|
+
end
|
72
|
+
|
73
|
+
specify 'lifecycle triggers are called only once' do
|
74
|
+
system.booter.boot!(:db)
|
75
|
+
system.booter.boot!(:db)
|
76
|
+
|
77
|
+
system.booter.boot(:db)
|
78
|
+
system.booter.boot(:db)
|
79
|
+
|
80
|
+
expect(db).to have_received(:establish_connection).exactly(1)
|
81
|
+
expect(db).to have_received(:load).exactly(1)
|
82
|
+
|
83
|
+
expect(system.booter.(:db).statuses).to eql(%i[init start])
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'dry/system/container'
|
2
|
+
|
3
|
+
RSpec.describe Dry::System::Container, '.import' do
|
4
|
+
subject(:app) { Class.new(Dry::System::Container) }
|
5
|
+
|
6
|
+
let(:db) do
|
7
|
+
Class.new(Dry::System::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 system when it was not loaded in the imported container yet' do
|
41
|
+
class Test::Other < Dry::System::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::System::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.injector
|
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
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "dry/system/container"
|
2
|
+
|
3
|
+
RSpec.describe Dry::System::Container, ".injector" do
|
4
|
+
context "injector_options provided" do
|
5
|
+
it "builds an injector with the provided options" do
|
6
|
+
Test::Foo = Class.new
|
7
|
+
|
8
|
+
Test::Container = Class.new(Dry::System::Container) do
|
9
|
+
register "foo", Test::Foo.new
|
10
|
+
end
|
11
|
+
|
12
|
+
Test::Inject = Test::Container.injector(strategies: {
|
13
|
+
default: Dry::AutoInject::Strategies::Args,
|
14
|
+
australian: Dry::AutoInject::Strategies::Args
|
15
|
+
})
|
16
|
+
|
17
|
+
injected_class = Class.new do
|
18
|
+
include Test::Inject.australian["foo"]
|
19
|
+
end
|
20
|
+
|
21
|
+
obj = injected_class.new
|
22
|
+
expect(obj.foo).to be_a Test::Foo
|
23
|
+
|
24
|
+
another = Object.new
|
25
|
+
obj = injected_class.new(another)
|
26
|
+
expect(obj.foo).to eq another
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'dry/system/container'
|
2
|
+
|
3
|
+
RSpec.describe Dry::System::Container do
|
4
|
+
subject(:container) { Test::Container }
|
5
|
+
|
6
|
+
context 'with default core dir' do
|
7
|
+
before do
|
8
|
+
class Test::Container < Dry::System::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.injector
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '.require' do
|
22
|
+
it 'requires a single file' do
|
23
|
+
container.require(Pathname('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(Pathname('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 component file' do
|
38
|
+
component = container.component('test/foo')
|
39
|
+
required = false
|
40
|
+
container.require_component(component) do
|
41
|
+
required = true
|
42
|
+
end
|
43
|
+
expect(required).to be(true)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'raises when file does not exist' do
|
47
|
+
component = container.component('test/missing')
|
48
|
+
expect { container.require_component(component) }.to raise_error(
|
49
|
+
Dry::System::FileNotFoundError, /test\.missing/
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '.load_component' do
|
55
|
+
it 'loads and registers systems from configured load paths' do
|
56
|
+
container.load_component('test.foo')
|
57
|
+
|
58
|
+
expect(Test.const_defined?(:Foo)).to be(true)
|
59
|
+
expect(Test.const_defined?(:Dep)).to be(true)
|
60
|
+
|
61
|
+
expect(Test::Foo.new.dep).to be_instance_of(Test::Dep)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "raises an error if a system's file can't be found" do
|
65
|
+
expect { container.load_component('test.missing') }.to raise_error(
|
66
|
+
Dry::System::ComponentLoadError, /test\.missing/
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "is a no op if a matching system is already registered" do
|
71
|
+
container.register "test.no_matching_file", Object.new
|
72
|
+
|
73
|
+
expect { container.load_component("test.no_matching_file") }.not_to raise_error
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '.boot' do
|
79
|
+
before do
|
80
|
+
class Test::Container < Dry::System::Container
|
81
|
+
configure do |config|
|
82
|
+
config.root = SPEC_ROOT.join('fixtures/lazytest').realpath
|
83
|
+
end
|
84
|
+
|
85
|
+
load_paths!('lib')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'lazy-boot a given system' do
|
90
|
+
container.boot(:bar)
|
91
|
+
|
92
|
+
expect(Test.const_defined?(:Bar)).to be(true)
|
93
|
+
expect(container.key?('test.bar')).to be(false)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe '.boot!' do
|
98
|
+
shared_examples_for 'a booted system' do
|
99
|
+
it 'boots a given system and finalizes it' do
|
100
|
+
container.boot!(:bar)
|
101
|
+
|
102
|
+
expect(Test.const_defined?(:Bar)).to be(true)
|
103
|
+
expect(container['test.bar']).to eql('I was finalized')
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'expects a symbol identifier matching file name' do
|
107
|
+
expect {
|
108
|
+
container.boot!('bar')
|
109
|
+
}.to raise_error(ArgumentError, 'component identifier "bar" must be a symbol')
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'expects identifier to point to an existing boot file' do
|
113
|
+
expect {
|
114
|
+
container.boot!(:foo)
|
115
|
+
}.to raise_error(
|
116
|
+
ArgumentError,
|
117
|
+
'component identifier +foo+ is invalid or boot file is missing'
|
118
|
+
)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'with the default core dir' do
|
123
|
+
it_behaves_like 'a booted system' do
|
124
|
+
before do
|
125
|
+
class Test::Container < Dry::System::Container
|
126
|
+
configure do |config|
|
127
|
+
config.root = SPEC_ROOT.join('fixtures/test').realpath
|
128
|
+
end
|
129
|
+
|
130
|
+
load_paths!('lib')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'with a custom core dir' do
|
137
|
+
it_behaves_like 'a booted system' do
|
138
|
+
before do
|
139
|
+
class Test::Container < Dry::System::Container
|
140
|
+
configure do |config|
|
141
|
+
config.root = SPEC_ROOT.join('fixtures/other').realpath
|
142
|
+
config.system_dir = 'config'
|
143
|
+
end
|
144
|
+
|
145
|
+
load_paths!('lib')
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'passes container to the finalizer block' do
|
152
|
+
class Test::Container < Dry::System::Container
|
153
|
+
configure { |c| c.name = :awesome }
|
154
|
+
|
155
|
+
finalize(:foo) do |container|
|
156
|
+
register(:w00t, container.config.name)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
Test::Container.booter.(:foo)
|
161
|
+
|
162
|
+
expect(Test::Container[:w00t]).to be(:awesome)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
RSpec.describe Dry::System::Injector do
|
2
|
+
before do
|
3
|
+
class Test::Container < Dry::System::Container
|
4
|
+
configure do |config|
|
5
|
+
config.root = SPEC_ROOT.join('fixtures/test').realpath
|
6
|
+
end
|
7
|
+
|
8
|
+
load_paths! 'lib'
|
9
|
+
end
|
10
|
+
|
11
|
+
Test::Inject = Test::Container.injector
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'supports args injection by default' do
|
15
|
+
obj = Class.new do
|
16
|
+
include Test::Inject['test.dep']
|
17
|
+
end.new
|
18
|
+
|
19
|
+
expect(obj.dep).to be_a Test::Dep
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'supports args injection with explicit method' do
|
23
|
+
obj = Class.new do
|
24
|
+
include Test::Inject.args['test.dep']
|
25
|
+
end.new
|
26
|
+
|
27
|
+
expect(obj.dep).to be_a Test::Dep
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'supports hash injection' do
|
31
|
+
obj = Class.new do
|
32
|
+
include Test::Inject.hash['test.dep']
|
33
|
+
end.new
|
34
|
+
|
35
|
+
expect(obj.dep).to be_a Test::Dep
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'support kwargs injection' do
|
39
|
+
obj = Class.new do
|
40
|
+
include Test::Inject.kwargs['test.dep']
|
41
|
+
end.new
|
42
|
+
|
43
|
+
expect(obj.dep).to be_a Test::Dep
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'allows injection strategies to be swapped' do
|
47
|
+
obj = Class.new do
|
48
|
+
include Test::Inject.kwargs.hash['test.dep']
|
49
|
+
end.new
|
50
|
+
|
51
|
+
expect(obj.dep).to be_a Test::Dep
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'supports aliases' do
|
55
|
+
obj = Class.new do
|
56
|
+
include Test::Inject['test.dep', foo: 'test.dep']
|
57
|
+
end.new
|
58
|
+
|
59
|
+
expect(obj.dep).to be_a Test::Dep
|
60
|
+
expect(obj.foo).to be_a Test::Dep
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'singleton' do
|
64
|
+
it 'supports injection' do
|
65
|
+
obj = Class.new do
|
66
|
+
include Test::Inject[foo: 'test.singleton_dep']
|
67
|
+
end.new
|
68
|
+
|
69
|
+
expect(obj.foo).to be_a Test::SingletonDep
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'dry/system/loader'
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
RSpec.describe Dry::System::Loader, '#call' do
|
5
|
+
shared_examples_for 'object loader' do
|
6
|
+
let(:instance) { loader.call }
|
7
|
+
|
8
|
+
context 'not singleton' do
|
9
|
+
it 'returns a new instance of the constant' do
|
10
|
+
expect(instance).to be_instance_of(constant)
|
11
|
+
expect(instance).not_to be(loader.call)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'singleton' do
|
16
|
+
before { constant.send(:include, Singleton) }
|
17
|
+
|
18
|
+
it 'returns singleton instance' do
|
19
|
+
expect(instance).to be(constant.instance)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with a singular name' do
|
25
|
+
subject(:loader) { Dry::System::Loader.new('test/bar') }
|
26
|
+
|
27
|
+
let(:constant) { Test::Bar }
|
28
|
+
|
29
|
+
before do
|
30
|
+
module Test;class Bar;end;end
|
31
|
+
end
|
32
|
+
|
33
|
+
it_behaves_like 'object loader'
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with a plural name' do
|
37
|
+
subject(:loader) { Dry::System::Loader.new('test/bars') }
|
38
|
+
|
39
|
+
let(:constant) { Test::Bars }
|
40
|
+
|
41
|
+
before do
|
42
|
+
module Test;class Bars;end;end
|
43
|
+
end
|
44
|
+
|
45
|
+
it_behaves_like 'object loader'
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'with a constructor accepting args' do
|
49
|
+
subject(:loader) { Dry::System::Loader.new('test/bar') }
|
50
|
+
|
51
|
+
before do
|
52
|
+
module Test
|
53
|
+
Bar = Struct.new(:one, :two)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'passes args to the constructor' do
|
58
|
+
instance = loader.call(1, 2)
|
59
|
+
|
60
|
+
expect(instance.one).to be(1)
|
61
|
+
expect(instance.two).to be(2)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|