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,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require "dry/core/deprecations"
5
+ require_relative "loader"
6
+
7
+ module Dry
8
+ module System
9
+ module ProviderSources
10
+ # @api private
11
+ module Settings
12
+ InvalidSettingsError = Class.new(ArgumentError) do
13
+ # @api private
14
+ def initialize(errors)
15
+ message = <<~STR
16
+ Could not load settings. The following settings were invalid:
17
+
18
+ #{setting_errors(errors).join("\n")}
19
+ STR
20
+
21
+ super(message)
22
+ end
23
+
24
+ private
25
+
26
+ def setting_errors(errors)
27
+ errors.sort_by { |k, _| k }.map { |key, error| "#{key}: #{error}" }
28
+ end
29
+ end
30
+
31
+ # @api private
32
+ class Config
33
+ # @api private
34
+ def self.load(root:, env:, loader: Loader)
35
+ loader = loader.new(root: root, env: env)
36
+
37
+ new.tap do |settings_obj|
38
+ errors = {}
39
+
40
+ settings.to_a.each do |setting_name|
41
+ value = loader[setting_name.to_s.upcase]
42
+
43
+ begin
44
+ settings_obj.config.public_send(:"#{setting_name}=", value) if value
45
+ rescue => e # rubocop:disable Style/RescueStandardError
46
+ errors[setting_name] = e
47
+ end
48
+ end
49
+
50
+ raise InvalidSettingsError, errors unless errors.empty?
51
+ end
52
+ end
53
+
54
+ # rubocop:disable Layout/LineLength
55
+ def self.key(name, type)
56
+ Dry::Core::Deprecations.announce(
57
+ "Dry::System :settings provider source setting definition using `key`",
58
+ "Use `setting` instead, with dry-configurable `setting` options, e.g. `setting :my_setting, default: \"hello\", constructor: Types::String.constrained(min_length: 3)`",
59
+ tag: "dry-system",
60
+ uplevel: 1
61
+ )
62
+
63
+ setting(name, constructor: type)
64
+ end
65
+ # rubocop:enable Layout/LineLength
66
+
67
+ include Dry::Configurable
68
+
69
+ private
70
+
71
+ def method_missing(name, *args, &block)
72
+ if config.respond_to?(name)
73
+ config.public_send(name, *args, &block)
74
+ else
75
+ super
76
+ end
77
+ end
78
+
79
+ def respond_to_missing?(name, include_all = false)
80
+ config.respond_to?(name, include_all) || super
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/configurable"
4
+ require "dry/core/deprecations"
5
+
6
+ module Dry
7
+ module System
8
+ module ProviderSources
9
+ module Settings
10
+ # @api private
11
+ class Loader
12
+ # @api private
13
+ attr_reader :store
14
+
15
+ # @api private
16
+ def initialize(root:, env:, store: ENV)
17
+ @store = store
18
+ load_dotenv(root, env.to_sym)
19
+ end
20
+
21
+ # @api private
22
+ def [](key)
23
+ store[key]
24
+ end
25
+
26
+ private
27
+
28
+ def load_dotenv(root, env)
29
+ require "dotenv"
30
+ Dotenv.load(*dotenv_files(root, env)) if defined?(Dotenv)
31
+ rescue LoadError
32
+ Dry::Core::Deprecations.announce(
33
+ "Dry::System :settings provider now requires dotenv to to load settings from .env files`", # rubocop:disable Layout/LineLength
34
+ "Add `gem \"dotenv\"` to your application's `Gemfile`",
35
+ tag: "dry-system",
36
+ uplevel: 3
37
+ )
38
+ # Do nothing if dotenv is unavailable
39
+ end
40
+
41
+ def dotenv_files(root, env)
42
+ [
43
+ File.join(root, ".env.#{env}.local"),
44
+ (File.join(root, ".env.local") unless env == :test),
45
+ File.join(root, ".env.#{env}"),
46
+ File.join(root, ".env")
47
+ ].compact
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module System
5
+ module ProviderSources
6
+ module Settings
7
+ class Source < Dry::System::Provider::Source
8
+ setting :store
9
+
10
+ def prepare
11
+ require "dry/system/provider_sources/settings/config"
12
+ end
13
+
14
+ def start
15
+ register(:settings, settings.load(root: target.root, env: target.config.env))
16
+ end
17
+
18
+ def settings(&block)
19
+ # Save the block and evaluate it lazily to allow a provider with this source
20
+ # to `require` any necessary files for the block to evaluate correctly (e.g.
21
+ # requiring an app-specific types module for setting constructors)
22
+ if block
23
+ @settings_block = block
24
+ elsif @settings_class
25
+ @settings_class
26
+ elsif @settings_block
27
+ @settings_class = Class.new(Settings::Config, &@settings_block)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ Dry::System.register_provider_source(
37
+ :settings,
38
+ group: :dry_system,
39
+ source: Dry::System::ProviderSources::Settings::Source
40
+ )
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system"
4
+
5
+ Dry::System.register_provider_sources Pathname(__dir__).join("provider_sources").realpath
@@ -7,7 +7,7 @@ module Dry
7
7
  class Container
8
8
  # @api private
9
9
  module Stubs
10
- def finalize!(&block)
10
+ def finalize!(freeze: true, &block)
11
11
  super(freeze: false, &block)
12
12
  end
13
13
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Dry
4
4
  module System
5
- VERSION = "0.19.2"
5
+ VERSION = "0.23.0"
6
6
  end
7
7
  end
data/lib/dry/system.rb CHANGED
@@ -1,30 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/system/provider"
4
- require "dry/system/provider_registry"
3
+ require "dry/core/deprecations"
4
+ require_relative "system/provider_source_registry"
5
5
 
6
6
  module Dry
7
7
  module System
8
- # Register external component provider
8
+ # Registers the provider sources in the files under the given path
9
9
  #
10
10
  # @api public
11
- def self.register_provider(identifier, options)
12
- providers.register(identifier, options)
13
- providers[identifier].load_components
14
- self
11
+ def self.register_provider_sources(path)
12
+ provider_sources.load_sources(path)
15
13
  end
16
14
 
17
- # Register an external component that can be booted within other systems
15
+ def self.register_provider(_name, options)
16
+ Dry::Core::Deprecations.announce(
17
+ "Dry::System.register_provider",
18
+ "Use `Dry::System.register_provider_sources` instead",
19
+ tag: "dry-system",
20
+ uplevel: 1
21
+ )
22
+
23
+ register_provider_sources(options.fetch(:path))
24
+ end
25
+
26
+ # Registers a provider source, which can be used as the basis for other providers
18
27
  #
19
28
  # @api public
20
- def self.register_component(identifier, provider:, &block)
21
- providers[provider].register_component(identifier, block)
22
- self
29
+ def self.register_provider_source(name, group:, source: nil, &block)
30
+ if source && block
31
+ raise ArgumentError, "You must supply only a `source:` option or a block, not both"
32
+ end
33
+
34
+ if source
35
+ provider_sources.register(name: name, group: group, source: source)
36
+ else
37
+ provider_sources.register_from_block(
38
+ name: name,
39
+ group: group,
40
+ target_container: self,
41
+ &block
42
+ )
43
+ end
44
+ end
45
+
46
+ def self.register_component(name, provider:, &block)
47
+ Dry::Core::Deprecations.announce(
48
+ "Dry::System.register_component",
49
+ "Use `Dry::System.register_provider_source` instead",
50
+ tag: "dry-system",
51
+ uplevel: 1
52
+ )
53
+
54
+ register_provider_source(name, group: provider, &block)
23
55
  end
24
56
 
25
57
  # @api private
26
- def self.providers
27
- @providers ||= ProviderRegistry.new
58
+ def self.provider_sources
59
+ @provider_sources ||= ProviderSourceRegistry.new
28
60
  end
29
61
  end
30
62
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-system
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.2
4
+ version: 0.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-30 00:00:00.000000000 Z
11
+ date: 2022-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -44,40 +44,40 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.12'
47
+ version: '0.14'
48
48
  - - ">="
49
49
  - !ruby/object:Gem::Version
50
- version: 0.12.1
50
+ version: 0.14.0
51
51
  type: :runtime
52
52
  prerelease: false
53
53
  version_requirements: !ruby/object:Gem::Requirement
54
54
  requirements:
55
55
  - - "~>"
56
56
  - !ruby/object:Gem::Version
57
- version: '0.12'
57
+ version: '0.14'
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 0.12.1
60
+ version: 0.14.0
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: dry-container
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '0.7'
67
+ version: '0.9'
68
68
  - - ">="
69
69
  - !ruby/object:Gem::Version
70
- version: 0.7.2
70
+ version: 0.9.0
71
71
  type: :runtime
72
72
  prerelease: false
73
73
  version_requirements: !ruby/object:Gem::Requirement
74
74
  requirements:
75
75
  - - "~>"
76
76
  - !ruby/object:Gem::Version
77
- version: '0.7'
77
+ version: '0.9'
78
78
  - - ">="
79
79
  - !ruby/object:Gem::Version
80
- version: 0.7.2
80
+ version: 0.9.0
81
81
  - !ruby/object:Gem::Dependency
82
82
  name: dry-core
83
83
  requirement: !ruby/object:Gem::Requirement
@@ -188,25 +188,23 @@ files:
188
188
  - lib/dry-system.rb
189
189
  - lib/dry/system.rb
190
190
  - lib/dry/system/auto_registrar.rb
191
- - lib/dry/system/booter.rb
192
- - lib/dry/system/booter/component_registry.rb
193
191
  - lib/dry/system/component.rb
194
192
  - lib/dry/system/component_dir.rb
195
193
  - lib/dry/system/components.rb
196
- - lib/dry/system/components/bootable.rb
197
- - lib/dry/system/components/config.rb
198
194
  - lib/dry/system/config/component_dir.rb
199
195
  - lib/dry/system/config/component_dirs.rb
196
+ - lib/dry/system/config/namespace.rb
197
+ - lib/dry/system/config/namespaces.rb
200
198
  - lib/dry/system/constants.rb
201
199
  - lib/dry/system/container.rb
202
200
  - lib/dry/system/errors.rb
203
201
  - lib/dry/system/identifier.rb
204
202
  - lib/dry/system/importer.rb
205
- - lib/dry/system/lifecycle.rb
203
+ - lib/dry/system/indirect_component.rb
206
204
  - lib/dry/system/loader.rb
207
205
  - lib/dry/system/loader/autoloading.rb
208
206
  - lib/dry/system/magic_comments_parser.rb
209
- - lib/dry/system/manual_registrar.rb
207
+ - lib/dry/system/manifest_registrar.rb
210
208
  - lib/dry/system/plugins.rb
211
209
  - lib/dry/system/plugins/bootsnap.rb
212
210
  - lib/dry/system/plugins/dependency_graph.rb
@@ -216,13 +214,18 @@ files:
216
214
  - lib/dry/system/plugins/monitoring.rb
217
215
  - lib/dry/system/plugins/monitoring/proxy.rb
218
216
  - lib/dry/system/plugins/notifications.rb
217
+ - lib/dry/system/plugins/zeitwerk.rb
218
+ - lib/dry/system/plugins/zeitwerk/compat_inflector.rb
219
219
  - lib/dry/system/provider.rb
220
- - lib/dry/system/provider_registry.rb
221
- - lib/dry/system/settings.rb
222
- - lib/dry/system/settings/file_loader.rb
223
- - lib/dry/system/settings/file_parser.rb
220
+ - lib/dry/system/provider/source.rb
221
+ - lib/dry/system/provider/source_dsl.rb
222
+ - lib/dry/system/provider_registrar.rb
223
+ - lib/dry/system/provider_source_registry.rb
224
+ - lib/dry/system/provider_sources.rb
225
+ - lib/dry/system/provider_sources/settings.rb
226
+ - lib/dry/system/provider_sources/settings/config.rb
227
+ - lib/dry/system/provider_sources/settings/loader.rb
224
228
  - lib/dry/system/stubs.rb
225
- - lib/dry/system/system_components/settings.rb
226
229
  - lib/dry/system/version.rb
227
230
  homepage: https://dry-rb.org/gems/dry-system
228
231
  licenses:
@@ -240,7 +243,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
240
243
  requirements:
241
244
  - - ">="
242
245
  - !ruby/object:Gem::Version
243
- version: 2.5.0
246
+ version: 2.7.0
244
247
  required_rubygems_version: !ruby/object:Gem::Requirement
245
248
  requirements:
246
249
  - - ">="
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dry
4
- module System
5
- class Booter
6
- class ComponentRegistry
7
- include Enumerable
8
-
9
- attr_reader :components
10
-
11
- def initialize
12
- @components = []
13
- end
14
-
15
- def each(&block)
16
- components.each(&block)
17
- end
18
-
19
- def register(component)
20
- @components << component
21
- end
22
-
23
- def exists?(name)
24
- components.any? { |component| component.identifier == name }
25
- end
26
-
27
- def [](name)
28
- component = components.detect { |component| component.identifier == name }
29
-
30
- component || raise(InvalidComponentIdentifierError, name)
31
- end
32
- end
33
- end
34
- end
35
- end
@@ -1,200 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "dry/system/components/bootable"
4
- require "dry/system/errors"
5
- require "dry/system/constants"
6
- require "dry/system/lifecycle"
7
- require "dry/system/booter/component_registry"
8
- require "pathname"
9
-
10
- module Dry
11
- module System
12
- # Default booter implementation
13
- #
14
- # This is currently configured by default for every System::Container.
15
- # Booter objects are responsible for loading system/boot files and expose
16
- # an API for calling lifecycle triggers.
17
- #
18
- # @api private
19
- class Booter
20
- attr_reader :paths
21
-
22
- attr_reader :booted
23
-
24
- attr_reader :components
25
-
26
- # @api private
27
- def initialize(paths)
28
- @paths = paths
29
- @booted = []
30
- @components = ComponentRegistry.new
31
- end
32
-
33
- # @api private
34
- def register_component(component)
35
- components.register(component)
36
- self
37
- end
38
-
39
- # Returns a bootable component if it can be found or loaded, otherwise nil
40
- #
41
- # @return [Dry::System::Components::Bootable, nil]
42
- # @api private
43
- def find_component(name)
44
- name = name.to_sym
45
-
46
- return components[name] if components.exists?(name)
47
-
48
- return if finalized?
49
-
50
- require_boot_file(name)
51
-
52
- components[name] if components.exists?(name)
53
- end
54
-
55
- # @api private
56
- def finalize!
57
- boot_files.each do |path|
58
- load_component(path)
59
- end
60
-
61
- components.each do |component|
62
- start(component)
63
- end
64
-
65
- freeze
66
- end
67
-
68
- # @!method finalized?
69
- # Returns true if the booter has been finalized
70
- #
71
- # @return [Boolean]
72
- # @api private
73
- alias_method :finalized?, :frozen?
74
-
75
- # @api private
76
- def shutdown
77
- components.each do |component|
78
- next unless booted.include?(component)
79
-
80
- stop(component)
81
- end
82
- end
83
-
84
- # @api private
85
- def init(name_or_component)
86
- with_component(name_or_component) do |component|
87
- call(component) do
88
- component.init.finalize
89
- yield if block_given?
90
- end
91
-
92
- self
93
- end
94
- end
95
-
96
- # @api private
97
- def start(name_or_component)
98
- with_component(name_or_component) do |component|
99
- return self if booted.include?(component)
100
-
101
- init(name_or_component) do
102
- component.start
103
- end
104
-
105
- booted << component.finalize
106
-
107
- self
108
- end
109
- end
110
-
111
- # @api private
112
- def stop(name_or_component)
113
- call(name_or_component) do |component|
114
- raise ComponentNotStartedError, name_or_component unless booted.include?(component)
115
-
116
- component.stop
117
- booted.delete(component)
118
-
119
- yield if block_given?
120
- end
121
- end
122
-
123
- # @api private
124
- def call(name_or_component)
125
- with_component(name_or_component) do |component|
126
- raise ComponentFileMismatchError, name unless component
127
-
128
- yield(component) if block_given?
129
-
130
- component
131
- end
132
- end
133
-
134
- # @api private
135
- def boot_dependency(component)
136
- if (component = find_component(component.root_key))
137
- start(component)
138
- end
139
- end
140
-
141
- # Returns all boot files within the configured paths
142
- #
143
- # Searches for files in the order of the configured paths. In the case of multiple
144
- # identically-named boot files within different paths, the file found first will be
145
- # returned, and other matching files will be discarded.
146
- #
147
- # @return [Array<Pathname>]
148
- # @api public
149
- def boot_files
150
- @boot_files ||= paths.each_with_object([[], []]) { |path, (boot_files, loaded)|
151
- files = Dir["#{path}/#{RB_GLOB}"].sort
152
-
153
- files.each do |file|
154
- basename = File.basename(file)
155
-
156
- unless loaded.include?(basename)
157
- boot_files << Pathname(file)
158
- loaded << basename
159
- end
160
- end
161
- }.first
162
- end
163
-
164
- private
165
-
166
- def with_component(id_or_component)
167
- component =
168
- case id_or_component
169
- when Symbol
170
- require_boot_file(id_or_component) unless components.exists?(id_or_component)
171
- components[id_or_component]
172
- when Components::Bootable
173
- id_or_component
174
- end
175
-
176
- raise InvalidComponentError, id_or_component unless component
177
-
178
- yield(component)
179
- end
180
-
181
- def load_component(path)
182
- identifier = Pathname(path).basename(RB_EXT).to_s.to_sym
183
-
184
- Kernel.require path unless components.exists?(identifier)
185
-
186
- self
187
- end
188
-
189
- def require_boot_file(identifier)
190
- boot_file = find_boot_file(identifier)
191
-
192
- Kernel.require boot_file if boot_file
193
- end
194
-
195
- def find_boot_file(name)
196
- boot_files.detect { |file| File.basename(file, RB_EXT) == name.to_s }
197
- end
198
- end
199
- end
200
- end