dry-system 0.19.2 → 0.23.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 +4 -4
- data/CHANGELOG.md +472 -1
- data/LICENSE +1 -1
- data/README.md +4 -3
- data/dry-system.gemspec +16 -15
- data/lib/dry/system/auto_registrar.rb +1 -13
- data/lib/dry/system/component.rb +104 -47
- data/lib/dry/system/component_dir.rb +88 -47
- data/lib/dry/system/components.rb +8 -4
- data/lib/dry/system/config/component_dir.rb +141 -53
- data/lib/dry/system/config/component_dirs.rb +176 -70
- data/lib/dry/system/config/namespace.rb +76 -0
- data/lib/dry/system/config/namespaces.rb +208 -0
- data/lib/dry/system/constants.rb +2 -2
- data/lib/dry/system/container.rb +279 -201
- data/lib/dry/system/errors.rb +72 -61
- data/lib/dry/system/identifier.rb +99 -79
- data/lib/dry/system/importer.rb +83 -12
- data/lib/dry/system/indirect_component.rb +65 -0
- data/lib/dry/system/loader.rb +8 -4
- data/lib/dry/system/{manual_registrar.rb → manifest_registrar.rb} +12 -13
- data/lib/dry/system/plugins/bootsnap.rb +3 -2
- data/lib/dry/system/plugins/dependency_graph/strategies.rb +37 -1
- data/lib/dry/system/plugins/dependency_graph.rb +26 -20
- data/lib/dry/system/plugins/env.rb +3 -2
- data/lib/dry/system/plugins/logging.rb +9 -5
- data/lib/dry/system/plugins/monitoring.rb +1 -1
- data/lib/dry/system/plugins/notifications.rb +1 -1
- data/lib/dry/system/plugins/zeitwerk/compat_inflector.rb +22 -0
- data/lib/dry/system/plugins/zeitwerk.rb +109 -0
- data/lib/dry/system/plugins.rb +8 -7
- data/lib/dry/system/provider/source.rb +324 -0
- data/lib/dry/system/provider/source_dsl.rb +94 -0
- data/lib/dry/system/provider.rb +264 -24
- data/lib/dry/system/provider_registrar.rb +276 -0
- data/lib/dry/system/provider_source_registry.rb +70 -0
- data/lib/dry/system/provider_sources/settings/config.rb +86 -0
- data/lib/dry/system/provider_sources/settings/loader.rb +53 -0
- data/lib/dry/system/provider_sources/settings.rb +40 -0
- data/lib/dry/system/provider_sources.rb +5 -0
- data/lib/dry/system/stubs.rb +1 -1
- data/lib/dry/system/version.rb +1 -1
- data/lib/dry/system.rb +45 -13
- metadata +25 -22
- data/lib/dry/system/booter/component_registry.rb +0 -35
- data/lib/dry/system/booter.rb +0 -200
- data/lib/dry/system/components/bootable.rb +0 -289
- data/lib/dry/system/components/config.rb +0 -35
- data/lib/dry/system/lifecycle.rb +0 -135
- data/lib/dry/system/provider_registry.rb +0 -27
- data/lib/dry/system/settings/file_loader.rb +0 -30
- data/lib/dry/system/settings/file_parser.rb +0 -51
- data/lib/dry/system/settings.rb +0 -67
- data/lib/dry/system/system_components/settings.rb +0 -11
@@ -0,0 +1,324 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/configurable"
|
4
|
+
require "dry/core/class_attributes"
|
5
|
+
require "dry/core/deprecations"
|
6
|
+
require_relative "source_dsl"
|
7
|
+
|
8
|
+
module Dry
|
9
|
+
module System
|
10
|
+
class Provider
|
11
|
+
# A provider's source provides the specific behavior for a given provider to serve
|
12
|
+
# its purpose.
|
13
|
+
#
|
14
|
+
# Sources should be subclasses of `Dry::System::Source::Provider`, with instance
|
15
|
+
# methods for each lifecycle step providing their behavior: {#prepare}, {#start},
|
16
|
+
# and {#stop}.
|
17
|
+
#
|
18
|
+
# Inside each of these methods, you should create and configure your provider's
|
19
|
+
# objects as required, and then {#register} them with the {#provider_container}.
|
20
|
+
# When the provider's lifecycle steps are run (via {Dry::System::Provider}), these
|
21
|
+
# registered components will be merged into the target container.
|
22
|
+
#
|
23
|
+
# You can prepare a provider's source in two ways:
|
24
|
+
#
|
25
|
+
# 1. Passing a bock when registering the provider, which is then evaluated via
|
26
|
+
# {Dry::System::Provider::SourceDSL} to prepare the provider subclass. This
|
27
|
+
# approach is easiest for simple providers.
|
28
|
+
# 2. Manually creare your own subclass of {Dry::System::Provider} and implement your
|
29
|
+
# own instance methods for the lifecycle steps (you should not implement your own
|
30
|
+
# `#initialize`). This approach may be useful for more complex providers.
|
31
|
+
#
|
32
|
+
# @see Dry::System::Container.register_provider
|
33
|
+
# @see Dry::System.register_provider_source
|
34
|
+
# @see Dry::System::Source::ProviderDSL
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
class Source
|
38
|
+
class << self
|
39
|
+
# Returns a new Dry::System::Provider::Source subclass with its behavior supplied by the
|
40
|
+
# given block, which is evaluated using Dry::System::Provider::SourceDSL.
|
41
|
+
#
|
42
|
+
# @see Dry::System::Provider::SourceDSL
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
def for(name:, group: nil, target_container:, &block) # rubocop:disable Style/KeywordParametersOrder
|
46
|
+
Class.new(self) { |klass|
|
47
|
+
klass.source_name name
|
48
|
+
klass.source_group group
|
49
|
+
SourceDSL.evaluate(klass, target_container, &block) if block
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def inherited(subclass)
|
54
|
+
super
|
55
|
+
|
56
|
+
# FIXME: This shouldn't _need_ to be in an inherited hook but right now it's
|
57
|
+
# the only way to prevent individual Source instances from sharing settings
|
58
|
+
subclass.include Dry::Configurable
|
59
|
+
end
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
def name
|
63
|
+
source_str = source_name
|
64
|
+
source_str = "#{source_group}->#{source_str}" if source_group
|
65
|
+
|
66
|
+
"Dry::System::Provider::Source[#{source_str}]"
|
67
|
+
end
|
68
|
+
|
69
|
+
# @api private
|
70
|
+
def to_s
|
71
|
+
"#<#{name}>"
|
72
|
+
end
|
73
|
+
|
74
|
+
# @api private
|
75
|
+
def inspect
|
76
|
+
to_s
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
CALLBACK_MAP = Hash.new { |h, k| h[k] = [] }.freeze
|
81
|
+
|
82
|
+
extend Dry::Core::ClassAttributes
|
83
|
+
|
84
|
+
defines :source_name, :source_group
|
85
|
+
|
86
|
+
# @api private
|
87
|
+
attr_reader :callbacks
|
88
|
+
|
89
|
+
# Returns the provider's own container for the provider.
|
90
|
+
#
|
91
|
+
# This container is namespaced based on the provider's `namespace:` configuration.
|
92
|
+
#
|
93
|
+
# Registered components in this container will be merged into the target container
|
94
|
+
# after the `prepare` and `start` lifecycle steps.
|
95
|
+
#
|
96
|
+
# @return [Dry::Container]
|
97
|
+
#
|
98
|
+
# @see #target_container
|
99
|
+
# @see Dry::System::Provider
|
100
|
+
#
|
101
|
+
# @api public
|
102
|
+
attr_reader :provider_container
|
103
|
+
alias_method :container, :provider_container
|
104
|
+
|
105
|
+
# Returns the target container for the provider.
|
106
|
+
#
|
107
|
+
# This is the container with which the provider is registered (via
|
108
|
+
# {Dry::System::Container.register_provider}).
|
109
|
+
#
|
110
|
+
# Registered components from the provider's container will be merged into this
|
111
|
+
# container after the `prepare` and `start` lifecycle steps.
|
112
|
+
#
|
113
|
+
# @return [Dry::System::Container]
|
114
|
+
#
|
115
|
+
# @see #provider_container
|
116
|
+
# @see Dry::System::Provider
|
117
|
+
#
|
118
|
+
# @api public
|
119
|
+
attr_reader :target_container
|
120
|
+
alias_method :target, :target_container
|
121
|
+
|
122
|
+
# @api private
|
123
|
+
def initialize(provider_container:, target_container:, &block)
|
124
|
+
super()
|
125
|
+
@callbacks = {before: CALLBACK_MAP.dup, after: CALLBACK_MAP.dup}
|
126
|
+
@provider_container = provider_container
|
127
|
+
@target_container = target_container
|
128
|
+
instance_exec(&block) if block
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns a string containing a human-readable representation of the provider.
|
132
|
+
#
|
133
|
+
# @return [String]
|
134
|
+
#
|
135
|
+
# @api private
|
136
|
+
def inspect
|
137
|
+
ivars = instance_variables.map { |ivar|
|
138
|
+
"#{ivar}=#{instance_variable_get(ivar).inspect}"
|
139
|
+
}.join(" ")
|
140
|
+
|
141
|
+
"#<#{self.class.name} #{ivars}>"
|
142
|
+
end
|
143
|
+
|
144
|
+
# Runs the behavior for the "prepare" lifecycle step.
|
145
|
+
#
|
146
|
+
# This should be implemented by your source subclass or specified by
|
147
|
+
# `SourceDSL#prepare` when registering a provider using a block.
|
148
|
+
#
|
149
|
+
# @return [void]
|
150
|
+
#
|
151
|
+
# @see SourceDSL#prepare
|
152
|
+
#
|
153
|
+
# @api public
|
154
|
+
def prepare; end
|
155
|
+
|
156
|
+
# Runs the behavior for the "start" lifecycle step.
|
157
|
+
#
|
158
|
+
# This should be implemented by your source subclass or specified by
|
159
|
+
# `SourceDSL#start` when registering a provider using a block.
|
160
|
+
#
|
161
|
+
# You can presume that {#prepare} has already run by the time this method is
|
162
|
+
# called.
|
163
|
+
#
|
164
|
+
# @return [void]
|
165
|
+
#
|
166
|
+
# @see SourceDSL#start
|
167
|
+
#
|
168
|
+
# @api public
|
169
|
+
def start; end
|
170
|
+
|
171
|
+
# Runs the behavior for the "stop" lifecycle step.
|
172
|
+
#
|
173
|
+
# This should be implemented by your source subclass or specified by
|
174
|
+
# `SourceDSL#stop` when registering a provider using a block.
|
175
|
+
#
|
176
|
+
# You can presume that {#prepare} and {#start} have already run by the time this
|
177
|
+
# method is called.
|
178
|
+
#
|
179
|
+
# @return [void]
|
180
|
+
#
|
181
|
+
# @see SourceDSL#stop
|
182
|
+
#
|
183
|
+
# @api public
|
184
|
+
def stop; end
|
185
|
+
|
186
|
+
def use(*provider_names)
|
187
|
+
Dry::Core::Deprecations.announce(
|
188
|
+
"Dry::System::Provider#use",
|
189
|
+
"Use `target_container.start` instead, e.g. `target_container.start(:another_provider)`", # rubocop:disable Layout/LineLength
|
190
|
+
tag: "dry-system",
|
191
|
+
uplevel: 1
|
192
|
+
)
|
193
|
+
|
194
|
+
provider_names.each do |name|
|
195
|
+
target_container.start(name)
|
196
|
+
end
|
197
|
+
|
198
|
+
self
|
199
|
+
end
|
200
|
+
|
201
|
+
# Registers a "before" callback for the given lifecycle step.
|
202
|
+
#
|
203
|
+
# The given block will be run before the lifecycle step method is run. The block
|
204
|
+
# will be evaluated in the context of the instance of this source.
|
205
|
+
#
|
206
|
+
# @param step_name [Symbol]
|
207
|
+
# @param block [Proc] the callback block
|
208
|
+
#
|
209
|
+
# @return [self]
|
210
|
+
#
|
211
|
+
# @see #after
|
212
|
+
#
|
213
|
+
# @api public
|
214
|
+
def before(step_name, &block)
|
215
|
+
if step_name.to_sym == :init
|
216
|
+
Dry::Core::Deprecations.announce(
|
217
|
+
"Dry::System::Provider before(:init) callback",
|
218
|
+
"Use `before(:prepare)` callback instead",
|
219
|
+
tag: "dry-system",
|
220
|
+
uplevel: 1
|
221
|
+
)
|
222
|
+
|
223
|
+
step_name = :prepare
|
224
|
+
end
|
225
|
+
|
226
|
+
callbacks[:before][step_name] << block
|
227
|
+
self
|
228
|
+
end
|
229
|
+
|
230
|
+
# Registers an "after" callback for the given lifecycle step.
|
231
|
+
#
|
232
|
+
# The given block will be run after the lifecycle step method is run. The block
|
233
|
+
# will be evaluated in the context of the instance of this source.
|
234
|
+
#
|
235
|
+
# @param step_name [Symbol]
|
236
|
+
# @param block [Proc] the callback block
|
237
|
+
#
|
238
|
+
# @return [self]
|
239
|
+
#
|
240
|
+
# @see #before
|
241
|
+
#
|
242
|
+
# @api public
|
243
|
+
def after(step_name, &block)
|
244
|
+
if step_name.to_sym == :init
|
245
|
+
Dry::Core::Deprecations.announce(
|
246
|
+
"Dry::System::Provider after(:init) callback",
|
247
|
+
"Use `after(:prepare)` callback instead",
|
248
|
+
tag: "dry-system",
|
249
|
+
uplevel: 1
|
250
|
+
)
|
251
|
+
|
252
|
+
step_name = :prepare
|
253
|
+
end
|
254
|
+
|
255
|
+
callbacks[:after][step_name] << block
|
256
|
+
self
|
257
|
+
end
|
258
|
+
|
259
|
+
# @api private
|
260
|
+
def run_callback(hook, step)
|
261
|
+
callbacks[hook][step].each do |callback|
|
262
|
+
if callback.parameters.any?
|
263
|
+
Dry::Core::Deprecations.announce(
|
264
|
+
"Dry::System::Provider::Source.before and .after callbacks with single block parameter", # rubocop:disable Layout/LineLength
|
265
|
+
"Use `provider_container` (or `container` for short) inside your block instead",
|
266
|
+
tag: "dry-system",
|
267
|
+
uplevel: 1
|
268
|
+
)
|
269
|
+
|
270
|
+
instance_exec(provider_container, &callback)
|
271
|
+
else
|
272
|
+
instance_eval(&callback)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
private
|
278
|
+
|
279
|
+
# Registers a component in the provider container.
|
280
|
+
#
|
281
|
+
# When the provider's lifecycle steps are run (via {Dry::System::Provider}), these
|
282
|
+
# registered components will be merged into the target container.
|
283
|
+
#
|
284
|
+
# @return [Dry::Container] the provider container
|
285
|
+
#
|
286
|
+
# @api public
|
287
|
+
def register(...)
|
288
|
+
provider_container.register(...)
|
289
|
+
end
|
290
|
+
|
291
|
+
# Resolves a previously registered component from the provider container.
|
292
|
+
#
|
293
|
+
# @param key [String] the key for the component to resolve
|
294
|
+
#
|
295
|
+
# @return [Object] the previously registered component
|
296
|
+
#
|
297
|
+
# @api public
|
298
|
+
def resolve(key)
|
299
|
+
provider_container.resolve(key)
|
300
|
+
end
|
301
|
+
|
302
|
+
# @api private
|
303
|
+
def run_step_block(step_name)
|
304
|
+
step_block = self.class.step_blocks[step_name]
|
305
|
+
instance_eval(&step_block) if step_block
|
306
|
+
end
|
307
|
+
|
308
|
+
# @api private
|
309
|
+
def method_missing(name, *args, &block)
|
310
|
+
if container.key?(name)
|
311
|
+
container[name]
|
312
|
+
else
|
313
|
+
super
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# @api private
|
318
|
+
def respond_to_missing?(name, include_all = false)
|
319
|
+
container.key?(name) || super
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/deprecations"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module System
|
7
|
+
class Provider
|
8
|
+
# Configures a Dry::System::Provider::Source subclass using a DSL that makes it
|
9
|
+
# nicer to define source behaviour via a single block.
|
10
|
+
#
|
11
|
+
# @see Dry::System::Container.register_provider
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
class SourceDSL
|
15
|
+
extend Dry::Core::Deprecations["Dry::System::Provider::SourceDSL"]
|
16
|
+
|
17
|
+
def self.evaluate(source_class, target_container, &block)
|
18
|
+
if block.parameters.any?
|
19
|
+
Dry::Core::Deprecations.announce(
|
20
|
+
"Dry::System.register_provider with single block parameter",
|
21
|
+
"Use `target_container` (or `target` for short) inside your block instead",
|
22
|
+
tag: "dry-system"
|
23
|
+
)
|
24
|
+
new(source_class).instance_exec(target_container, &block)
|
25
|
+
else
|
26
|
+
new(source_class).instance_eval(&block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :source_class
|
31
|
+
|
32
|
+
def initialize(source_class)
|
33
|
+
@source_class = source_class
|
34
|
+
end
|
35
|
+
|
36
|
+
def setting(...)
|
37
|
+
source_class.setting(...)
|
38
|
+
end
|
39
|
+
|
40
|
+
# rubocop:disable Layout/LineLength
|
41
|
+
|
42
|
+
def settings(&block)
|
43
|
+
Dry::Core::Deprecations.announce(
|
44
|
+
"Dry::System.register_provider with nested settings block",
|
45
|
+
"Use individual top-level `setting` declarations instead (see dry-configurable docs for details)",
|
46
|
+
tag: "dry-system",
|
47
|
+
uplevel: 1
|
48
|
+
)
|
49
|
+
|
50
|
+
DeprecatedSettingsDSL.new(self).instance_eval(&block)
|
51
|
+
end
|
52
|
+
|
53
|
+
# rubocop:enable Layout/LineLength
|
54
|
+
|
55
|
+
class DeprecatedSettingsDSL
|
56
|
+
def initialize(base_dsl)
|
57
|
+
@base_dsl = base_dsl
|
58
|
+
end
|
59
|
+
|
60
|
+
def key(name, type)
|
61
|
+
@base_dsl.setting(name, constructor: type)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def prepare(&block)
|
66
|
+
source_class.define_method(:prepare, &block)
|
67
|
+
end
|
68
|
+
deprecate :init, :prepare
|
69
|
+
|
70
|
+
def start(&block)
|
71
|
+
source_class.define_method(:start, &block)
|
72
|
+
end
|
73
|
+
|
74
|
+
def stop(&block)
|
75
|
+
source_class.define_method(:stop, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def method_missing(name, *args, &block)
|
81
|
+
if source_class.respond_to?(name)
|
82
|
+
source_class.public_send(name, *args, &block)
|
83
|
+
else
|
84
|
+
super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def respond_to_missing?(name, include_all = false)
|
89
|
+
source_class.respond_to?(name, include_all) || super
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|