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,62 @@
|
|
1
|
+
module Dry
|
2
|
+
module System
|
3
|
+
# Error raised when the container tries to load a component with missing
|
4
|
+
# file
|
5
|
+
#
|
6
|
+
# @api public
|
7
|
+
FileNotFoundError = Class.new(StandardError) do
|
8
|
+
def initialize(component)
|
9
|
+
super("could not resolve require file for #{component.identifier}")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Error raised when a resolved component couldn't be found
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
ComponentLoadError = Class.new(StandardError) do
|
17
|
+
def initialize(component)
|
18
|
+
super("could not load component #{component.inspect}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Error raised when invalid namespace name was provided
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
InvalidNamespaceError = Class.new(StandardError) do
|
26
|
+
def initialize(ns)
|
27
|
+
super("Namespace #{ns} cannot include a separator")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Error raised when resolved component couldn't be loaded
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
InvalidComponentError = Class.new(ArgumentError) do
|
35
|
+
def initialize(name, reason = nil)
|
36
|
+
super(
|
37
|
+
"Tried to create an invalid #{name.inspect} component - #{reason}"
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Error raised when component's identifier is not valid
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
InvalidComponentIdentifierError = Class.new(ArgumentError) do
|
46
|
+
def initialize(name)
|
47
|
+
super(
|
48
|
+
"component identifier +#{name}+ is invalid or boot file is missing"
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Error raised when component's identifier for booting is not a symbol
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
InvalidComponentIdentifierTypeError = Class.new(ArgumentError) do
|
57
|
+
def initialize(name)
|
58
|
+
super("component identifier #{name.inspect} must be a symbol")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Dry
|
2
|
+
module System
|
3
|
+
# Default importer implementation
|
4
|
+
#
|
5
|
+
# This is currently configured by default for every System::Container.
|
6
|
+
# Importer objects are responsible for importing components from one
|
7
|
+
# container to another. This is used in cases where an application is split
|
8
|
+
# into multiple sub-systems.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Importer
|
12
|
+
attr_reader :container
|
13
|
+
|
14
|
+
attr_reader :separator
|
15
|
+
|
16
|
+
attr_reader :registry
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
def initialize(container)
|
20
|
+
@container = container
|
21
|
+
@separator = container.config.namespace_separator
|
22
|
+
@registry = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
def finalize!
|
27
|
+
registry.each do |name, container|
|
28
|
+
call(name, container.finalize!)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
def [](name)
|
34
|
+
registry.fetch(name)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api private
|
38
|
+
def key?(name)
|
39
|
+
registry.key?(name)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def call(ns, other)
|
44
|
+
container.merge(other, namespace: ns)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @api private
|
48
|
+
def register(other)
|
49
|
+
registry.update(other)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "dry-auto_inject"
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module System
|
5
|
+
# Injection mixin builder
|
6
|
+
#
|
7
|
+
# Injector objects are created by containers and can be used to automatically
|
8
|
+
# define object constructors where depedencies will be injected in.
|
9
|
+
#
|
10
|
+
# Main purpose of this object is to provide injection along with lazy-loading
|
11
|
+
# of components on demand. This gives us a way to load components in
|
12
|
+
# isolation from the rest of the system.
|
13
|
+
#
|
14
|
+
# @see [Container.injector]
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
class Injector < BasicObject
|
18
|
+
# @api private
|
19
|
+
attr_reader :container
|
20
|
+
|
21
|
+
# @api private
|
22
|
+
attr_reader :options
|
23
|
+
|
24
|
+
# @api private
|
25
|
+
attr_reader :injector
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
def initialize(container, options: {}, strategy: :default)
|
29
|
+
@container = container
|
30
|
+
@options = options
|
31
|
+
@injector = ::Dry::AutoInject(container, options).__send__(strategy)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Create injection mixin for specified dependencies
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# require 'system/import'
|
38
|
+
#
|
39
|
+
# class UserRepo
|
40
|
+
# include Import['persistence.db']
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# @param [Array<String>] *deps Keys under which dependencies are registered
|
44
|
+
#
|
45
|
+
# @return [Dry::AutoInject::Injector]
|
46
|
+
#
|
47
|
+
# @api public
|
48
|
+
def [](*deps)
|
49
|
+
load_components(*deps)
|
50
|
+
injector[*deps]
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# @api private
|
56
|
+
def method_missing(name, *args, &block)
|
57
|
+
::Dry::System::Injector.new(container, options: options, strategy: name)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @api private
|
61
|
+
def load_components(*keys, **aliases)
|
62
|
+
(keys + aliases.values).each do |key|
|
63
|
+
container.load_component(key)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'concurrent/map'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module System
|
5
|
+
# Lifecycle booting DSL
|
6
|
+
#
|
7
|
+
# Lifecycle objects are used in the boot files where you can register custom
|
8
|
+
# init/start/stop triggers
|
9
|
+
#
|
10
|
+
# @see [Container.finalize]
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
class Lifecycle < BasicObject
|
14
|
+
attr_reader :container
|
15
|
+
|
16
|
+
attr_reader :init
|
17
|
+
|
18
|
+
attr_reader :start
|
19
|
+
|
20
|
+
attr_reader :stop
|
21
|
+
|
22
|
+
attr_reader :statuses
|
23
|
+
|
24
|
+
attr_reader :triggers
|
25
|
+
|
26
|
+
# @api private
|
27
|
+
def self.new(container, &block)
|
28
|
+
cache.fetch_or_store([container, block].hash) do
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
def self.cache
|
35
|
+
@cache ||= ::Concurrent::Map.new
|
36
|
+
end
|
37
|
+
|
38
|
+
# @api private
|
39
|
+
def initialize(container, &block)
|
40
|
+
@container = container
|
41
|
+
@statuses = []
|
42
|
+
@triggers = {}
|
43
|
+
instance_exec(container, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @api private
|
47
|
+
def call(*triggers)
|
48
|
+
triggers.each do |trigger|
|
49
|
+
unless statuses.include?(trigger)
|
50
|
+
__send__(trigger)
|
51
|
+
statuses << trigger
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @api private
|
57
|
+
def init(&block)
|
58
|
+
trigger!(:init, &block)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
def start(&block)
|
63
|
+
trigger!(:start, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @api private
|
67
|
+
def stop(&block)
|
68
|
+
trigger!(:stop, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @api private
|
72
|
+
def use(*names)
|
73
|
+
names.each do |name|
|
74
|
+
container.boot!(name)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @api private
|
79
|
+
def register(*args, &block)
|
80
|
+
container.register(*args, &block)
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# @api private
|
86
|
+
def trigger!(name, &block)
|
87
|
+
if triggers.key?(name)
|
88
|
+
triggers[name].()
|
89
|
+
elsif block
|
90
|
+
triggers[name] = block
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# @api private
|
95
|
+
def method_missing(meth, *args, &block)
|
96
|
+
if container.key?(meth)
|
97
|
+
container[meth]
|
98
|
+
elsif ::Kernel.respond_to?(meth)
|
99
|
+
::Kernel.public_send(meth, *args, &block)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'inflecto'
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module System
|
5
|
+
# Default component loader implementation
|
6
|
+
#
|
7
|
+
# This class is configured by default for every System::Container. You can
|
8
|
+
# provide your own and use it in your containers too.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# class MyLoader < Dry::System::Loader
|
12
|
+
# def call(*args)
|
13
|
+
# constant.build(*args)
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# class MyApp < Dry::System::Container
|
18
|
+
# configure do |config|
|
19
|
+
# # ...
|
20
|
+
# config.loader MyLoader
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
class Loader
|
26
|
+
# @!attribute [r] path
|
27
|
+
# @return [String] Path to component's file
|
28
|
+
attr_reader :path
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
def initialize(path)
|
32
|
+
@path = path
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns component's instance
|
36
|
+
#
|
37
|
+
# Provided optional args are passed to object's constructor
|
38
|
+
#
|
39
|
+
# @param [Array] *args Optional constructor args
|
40
|
+
#
|
41
|
+
# @return [Object]
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
def call(*args)
|
45
|
+
if singleton?(constant)
|
46
|
+
constant.instance(*args)
|
47
|
+
else
|
48
|
+
constant.new(*args)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return component's class constant
|
53
|
+
#
|
54
|
+
# @return [Class]
|
55
|
+
#
|
56
|
+
# @api public
|
57
|
+
def constant
|
58
|
+
Inflecto.constantize(Inflecto.camelize(path))
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# @api private
|
64
|
+
def singleton?(constant)
|
65
|
+
constant.respond_to?(:instance) && !constant.respond_to?(:new)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|