dry-system 0.19.2 → 0.23.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +472 -1
  3. data/LICENSE +1 -1
  4. data/README.md +4 -3
  5. data/dry-system.gemspec +16 -15
  6. data/lib/dry/system/auto_registrar.rb +1 -13
  7. data/lib/dry/system/component.rb +104 -47
  8. data/lib/dry/system/component_dir.rb +88 -47
  9. data/lib/dry/system/components.rb +8 -4
  10. data/lib/dry/system/config/component_dir.rb +141 -53
  11. data/lib/dry/system/config/component_dirs.rb +176 -70
  12. data/lib/dry/system/config/namespace.rb +76 -0
  13. data/lib/dry/system/config/namespaces.rb +208 -0
  14. data/lib/dry/system/constants.rb +2 -2
  15. data/lib/dry/system/container.rb +279 -201
  16. data/lib/dry/system/errors.rb +72 -61
  17. data/lib/dry/system/identifier.rb +99 -79
  18. data/lib/dry/system/importer.rb +83 -12
  19. data/lib/dry/system/indirect_component.rb +65 -0
  20. data/lib/dry/system/loader.rb +8 -4
  21. data/lib/dry/system/{manual_registrar.rb → manifest_registrar.rb} +12 -13
  22. data/lib/dry/system/plugins/bootsnap.rb +3 -2
  23. data/lib/dry/system/plugins/dependency_graph/strategies.rb +37 -1
  24. data/lib/dry/system/plugins/dependency_graph.rb +26 -20
  25. data/lib/dry/system/plugins/env.rb +3 -2
  26. data/lib/dry/system/plugins/logging.rb +9 -5
  27. data/lib/dry/system/plugins/monitoring.rb +1 -1
  28. data/lib/dry/system/plugins/notifications.rb +1 -1
  29. data/lib/dry/system/plugins/zeitwerk/compat_inflector.rb +22 -0
  30. data/lib/dry/system/plugins/zeitwerk.rb +109 -0
  31. data/lib/dry/system/plugins.rb +8 -7
  32. data/lib/dry/system/provider/source.rb +324 -0
  33. data/lib/dry/system/provider/source_dsl.rb +94 -0
  34. data/lib/dry/system/provider.rb +264 -24
  35. data/lib/dry/system/provider_registrar.rb +276 -0
  36. data/lib/dry/system/provider_source_registry.rb +70 -0
  37. data/lib/dry/system/provider_sources/settings/config.rb +86 -0
  38. data/lib/dry/system/provider_sources/settings/loader.rb +53 -0
  39. data/lib/dry/system/provider_sources/settings.rb +40 -0
  40. data/lib/dry/system/provider_sources.rb +5 -0
  41. data/lib/dry/system/stubs.rb +1 -1
  42. data/lib/dry/system/version.rb +1 -1
  43. data/lib/dry/system.rb +45 -13
  44. metadata +25 -22
  45. data/lib/dry/system/booter/component_registry.rb +0 -35
  46. data/lib/dry/system/booter.rb +0 -200
  47. data/lib/dry/system/components/bootable.rb +0 -289
  48. data/lib/dry/system/components/config.rb +0 -35
  49. data/lib/dry/system/lifecycle.rb +0 -135
  50. data/lib/dry/system/provider_registry.rb +0 -27
  51. data/lib/dry/system/settings/file_loader.rb +0 -30
  52. data/lib/dry/system/settings/file_parser.rb +0 -51
  53. data/lib/dry/system/settings.rb +0 -67
  54. 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