dry-system 0.12.0 → 0.13.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +101 -86
  3. data/README.md +2 -2
  4. data/lib/dry-system.rb +2 -0
  5. data/lib/dry/system.rb +3 -1
  6. data/lib/dry/system/auto_registrar.rb +12 -8
  7. data/lib/dry/system/auto_registrar/configuration.rb +2 -0
  8. data/lib/dry/system/booter.rb +10 -9
  9. data/lib/dry/system/booter/component_registry.rb +3 -5
  10. data/lib/dry/system/component.rb +5 -2
  11. data/lib/dry/system/components.rb +2 -0
  12. data/lib/dry/system/components/bootable.rb +13 -11
  13. data/lib/dry/system/components/config.rb +2 -0
  14. data/lib/dry/system/constants.rb +8 -7
  15. data/lib/dry/system/container.rb +64 -25
  16. data/lib/dry/system/errors.rb +9 -1
  17. data/lib/dry/system/importer.rb +2 -0
  18. data/lib/dry/system/lifecycle.rb +3 -1
  19. data/lib/dry/system/loader.rb +2 -0
  20. data/lib/dry/system/magic_comments_parser.rb +3 -3
  21. data/lib/dry/system/manual_registrar.rb +3 -1
  22. data/lib/dry/system/plugins.rb +9 -4
  23. data/lib/dry/system/plugins/bootsnap.rb +4 -1
  24. data/lib/dry/system/plugins/dependency_graph.rb +47 -0
  25. data/lib/dry/system/plugins/dependency_graph/strategies.rb +30 -0
  26. data/lib/dry/system/plugins/env.rb +2 -0
  27. data/lib/dry/system/plugins/logging.rb +8 -7
  28. data/lib/dry/system/plugins/monitoring.rb +3 -1
  29. data/lib/dry/system/plugins/monitoring/proxy.rb +2 -0
  30. data/lib/dry/system/plugins/notifications.rb +4 -1
  31. data/lib/dry/system/provider.rb +3 -1
  32. data/lib/dry/system/provider_registry.rb +2 -0
  33. data/lib/dry/system/settings.rb +9 -7
  34. data/lib/dry/system/settings/file_loader.rb +4 -2
  35. data/lib/dry/system/settings/file_parser.rb +5 -3
  36. data/lib/dry/system/stubs.rb +2 -0
  37. data/lib/dry/system/system_components/settings.rb +2 -0
  38. data/lib/dry/system/version.rb +3 -1
  39. metadata +25 -17
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module System
3
5
  class Booter
@@ -25,11 +27,7 @@ module Dry
25
27
  def [](name)
26
28
  component = components.detect { |component| component.identifier == name }
27
29
 
28
- if component
29
- component
30
- else
31
- raise InvalidComponentIdentifierError, name
32
- end
30
+ component || raise(InvalidComponentIdentifierError, name)
33
31
  end
34
32
  end
35
33
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'concurrent/map'
2
4
 
3
5
  require 'dry-equalizer'
@@ -83,9 +85,10 @@ module Dry
83
85
 
84
86
  # @api private
85
87
  def initialize(identifier, path, options)
86
- @identifier, @path = identifier, path
88
+ @identifier = identifier
89
+ @path = path
87
90
  @options = options
88
- @file = "#{path}#{RB_EXT}".freeze
91
+ @file = "#{path}#{RB_EXT}"
89
92
  @loader = options.fetch(:loader)
90
93
  freeze
91
94
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/system'
2
4
 
3
5
  Dry::System.register_provider(
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/system/lifecycle'
2
4
  require 'dry/system/settings'
3
5
  require 'dry/system/components/config'
@@ -65,10 +67,12 @@ module Dry
65
67
  # @return [Symbol,String] default namespace for the container keys
66
68
  attr_reader :namespace
67
69
 
70
+ TRIGGER_MAP = Hash.new { |h, k| h[k] = [] }.freeze
71
+
68
72
  # @api private
69
73
  def initialize(identifier, options = {}, &block)
70
74
  @identifier = identifier
71
- @triggers = { before: Hash.new { |h, k| h[k] = [] }, after: Hash.new { |h, k| h[k] = [] } }
75
+ @triggers = { before: TRIGGER_MAP.dup, after: TRIGGER_MAP.dup }
72
76
  @options = block ? options.merge(block: block) : options
73
77
  @namespace = options[:namespace]
74
78
  finalize = options[:finalize] || DEFAULT_FINALIZE
@@ -159,11 +163,7 @@ module Dry
159
163
  #
160
164
  # @api public
161
165
  def config
162
- if @config
163
- @config
164
- else
165
- configure!
166
- end
166
+ @config || configure!
167
167
  end
168
168
 
169
169
  # Return a list of lifecycle steps that were executed
@@ -191,7 +191,7 @@ module Dry
191
191
  # @api private
192
192
  def finalize
193
193
  lifecycle.container.each do |key, item|
194
- container.register(key, item) unless container.key?(key)
194
+ container.register(key, item) unless container.registered?(key)
195
195
  end
196
196
  self
197
197
  end
@@ -241,8 +241,8 @@ module Dry
241
241
  #
242
242
  # @api private
243
243
  def boot_file
244
- container_boot_files.
245
- detect { |path| Pathname(path).basename(RB_EXT).to_s == identifier.to_s }
244
+ container_boot_files
245
+ .detect { |path| Pathname(path).basename(RB_EXT).to_s == identifier.to_s }
246
246
  end
247
247
 
248
248
  # Return path to boot dir
@@ -260,7 +260,7 @@ module Dry
260
260
  #
261
261
  # @api private
262
262
  def container_boot_files
263
- Dir[container.boot_path.join("**/#{RB_GLOB}")]
263
+ ::Dir[container.boot_path.join("**/#{RB_GLOB}")].sort
264
264
  end
265
265
 
266
266
  private
@@ -290,7 +290,9 @@ module Dry
290
290
  when nil
291
291
  container
292
292
  else
293
- raise RuntimeError, "+namespace+ boot option must be true, string or symbol #{namespace.inspect} given."
293
+ raise <<-STR
294
+ +namespace+ boot option must be true, string or symbol #{namespace.inspect} given.
295
+ STR
294
296
  end
295
297
  end
296
298
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module System
3
5
  module Components
@@ -1,24 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/core/constants'
2
4
 
3
5
  module Dry
4
6
  module System
5
7
  include Dry::Core::Constants
6
8
 
7
- RB_EXT = '.rb'.freeze
8
- RB_GLOB = '*.rb'.freeze
9
- PATH_SEPARATOR = '/'.freeze
10
- DEFAULT_SEPARATOR = '.'.freeze
9
+ RB_EXT = '.rb'
10
+ RB_GLOB = '*.rb'
11
+ PATH_SEPARATOR = '/'
12
+ DEFAULT_SEPARATOR = '.'
11
13
  WORD_REGEX = /\w+/.freeze
12
14
 
13
15
  DuplicatedComponentKeyError = Class.new(ArgumentError)
14
16
  InvalidSettingsError = Class.new(ArgumentError) do
15
17
  # @api private
16
18
  def initialize(attributes)
17
- message = <<~EOF
19
+ message = <<~STR
18
20
  Could not initialize settings. The following settings were invalid:
19
21
 
20
22
  #{attributes_errors(attributes).join("\n")}
21
- EOF
23
+ STR
22
24
  super(message)
23
25
  end
24
26
 
@@ -38,6 +40,5 @@ module Dry
38
40
  super("dry-system plugin #{plugin.inspect} failed to load its dependencies: #{message}")
39
41
  end
40
42
  end
41
-
42
43
  end
43
44
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pathname'
2
4
 
3
5
  require 'dry-auto_inject'
@@ -73,8 +75,8 @@ module Dry
73
75
  setting :name
74
76
  setting :default_namespace
75
77
  setting(:root, Pathname.pwd.freeze) { |path| Pathname(path) }
76
- setting :system_dir, 'system'.freeze
77
- setting :registrations_dir, 'container'.freeze
78
+ setting :system_dir, 'system'
79
+ setting :registrations_dir, 'container'
78
80
  setting :auto_register, []
79
81
  setting :inflector, Dry::Inflector.new
80
82
  setting :loader, Dry::System::Loader
@@ -82,9 +84,17 @@ module Dry
82
84
  setting :auto_registrar, Dry::System::AutoRegistrar
83
85
  setting :manual_registrar, Dry::System::ManualRegistrar
84
86
  setting :importer, Dry::System::Importer
85
- setting(:components, {}, reader: true) { |v| v.dup }
87
+ setting(:components, {}, reader: true, &:dup)
86
88
 
87
89
  class << self
90
+ def strategies(value = nil)
91
+ if value
92
+ @strategies = value
93
+ else
94
+ @strategies ||= Dry::AutoInject::Strategies
95
+ end
96
+ end
97
+
88
98
  extend Dry::Core::Deprecations['Dry::System::Container']
89
99
 
90
100
  # Configures the container
@@ -139,7 +149,9 @@ module Dry
139
149
  when Hash then importer.register(other)
140
150
  when Dry::Container::Namespace then super
141
151
  else
142
- raise ArgumentError, "+other+ must be a hash of names and systems, or a Dry::Container namespace"
152
+ raise ArgumentError, <<-STR
153
+ +other+ must be a hash of names and systems, or a Dry::Container namespace
154
+ STR
143
155
  end
144
156
  end
145
157
 
@@ -218,7 +230,9 @@ module Dry
218
230
  # @api public
219
231
  def boot(name, opts = {}, &block)
220
232
  if components.key?(name)
221
- raise DuplicatedComponentKeyError, "Bootable component #{name.inspect} was already registered"
233
+ raise DuplicatedComponentKeyError, <<-STR
234
+ Bootable component #{name.inspect} was already registered
235
+ STR
222
236
  end
223
237
 
224
238
  component =
@@ -227,7 +241,6 @@ module Dry
227
241
  else
228
242
  boot_local(name, opts, &block)
229
243
  end
230
- self
231
244
 
232
245
  components[name] = component
233
246
  end
@@ -246,7 +259,9 @@ module Dry
246
259
 
247
260
  # @api private
248
261
  def boot_local(identifier, namespace: nil, &block)
249
- component = Components::Bootable.new(identifier, container: self, namespace: namespace, &block)
262
+ component = Components::Bootable.new(
263
+ identifier, container: self, namespace: namespace, &block
264
+ )
250
265
 
251
266
  booter.register_component(component)
252
267
 
@@ -381,6 +396,7 @@ module Dry
381
396
  def load_paths!(*dirs)
382
397
  dirs.map(&root.method(:join)).each do |path|
383
398
  next if load_paths.include?(path)
399
+
384
400
  load_paths << path
385
401
  $LOAD_PATH.unshift(path.to_s)
386
402
  end
@@ -459,7 +475,7 @@ module Dry
459
475
  # @param options [Hash] injector options
460
476
  #
461
477
  # @api public
462
- def injector(options = {})
478
+ def injector(options = { strategies: strategies })
463
479
  Dry::AutoInject(self, options)
464
480
  end
465
481
 
@@ -477,7 +493,7 @@ module Dry
477
493
  # @api public
478
494
  def require_from_root(*paths)
479
495
  paths.flat_map { |path|
480
- path.to_s.include?('*') ? Dir[root.join(path)] : root.join(path)
496
+ path.to_s.include?('*') ? ::Dir[root.join(path)].sort : root.join(path)
481
497
  }.each { |path|
482
498
  require path.to_s
483
499
  }
@@ -502,12 +518,37 @@ module Dry
502
518
  end
503
519
 
504
520
  # @api public
505
- def resolve(key)
506
- load_component(key) unless finalized?
521
+ def resolve(key, &block)
522
+ load_component(key, &block) unless finalized?
507
523
 
508
524
  super
509
525
  end
510
526
 
527
+ alias_method :registered?, :key?
528
+ #
529
+ # @!method registered?(key)
530
+ # Whether a +key+ is registered (doesn't trigger loading)
531
+ # @param [String,Symbol] key Identifier
532
+ # @return [Boolean]
533
+ # @api public
534
+ #
535
+
536
+ # Check if identifier is registered.
537
+ # If not, try to load the component
538
+ #
539
+ # @param [String,Symbol] key Identifier
540
+ # @return [Boolean]
541
+ #
542
+ # @api public
543
+ def key?(key)
544
+ if finalized?
545
+ registered?(key)
546
+ else
547
+ registered?(key) || resolve(key) { return false }
548
+ true
549
+ end
550
+ end
551
+
511
552
  # @api private
512
553
  def load_paths
513
554
  @load_paths ||= []
@@ -549,18 +590,16 @@ module Dry
549
590
  namespace: config.default_namespace,
550
591
  separator: config.namespace_separator,
551
592
  inflector: config.inflector,
552
- **options,
593
+ **options
553
594
  )
554
595
  end
555
596
  end
556
597
 
557
598
  # @api private
558
599
  def require_component(component)
559
- return if key?(component.identifier)
600
+ return if registered?(component.identifier)
560
601
 
561
- unless component.file_exists?(load_paths)
562
- raise FileNotFoundError, component
563
- end
602
+ raise FileNotFoundError, component unless component.file_exists?(load_paths)
564
603
 
565
604
  require_path(component.path)
566
605
 
@@ -579,8 +618,8 @@ module Dry
579
618
  end
580
619
 
581
620
  # @api private
582
- def load_component(key)
583
- return self if key?(key)
621
+ def load_component(key, &block)
622
+ return self if registered?(key)
584
623
 
585
624
  component(key).tap do |component|
586
625
  if component.boot?
@@ -594,9 +633,7 @@ module Dry
594
633
  load_imported_component(component.namespaced(root_key))
595
634
  end
596
635
 
597
- if !key?(key)
598
- load_local_component(component)
599
- end
636
+ load_local_component(component, &block) unless registered?(key)
600
637
  end
601
638
  end
602
639
 
@@ -610,7 +647,7 @@ module Dry
610
647
 
611
648
  # @api private
612
649
  def hooks
613
- @__hooks__ ||= Hash.new { |h, k| h[k] = [] }
650
+ @hooks ||= Hash.new { |h, k| h[k] = [] }
614
651
  end
615
652
 
616
653
  # @api private
@@ -622,14 +659,14 @@ module Dry
622
659
  new_hooks[event].concat(klass.hooks[event])
623
660
  end
624
661
 
625
- klass.instance_variable_set(:@__hooks__, new_hooks)
662
+ klass.instance_variable_set(:@hooks, new_hooks)
626
663
  super
627
664
  end
628
665
 
629
666
  private
630
667
 
631
668
  # @api private
632
- def load_local_component(component, default_namespace_fallback = false)
669
+ def load_local_component(component, default_namespace_fallback = false, &block)
633
670
  if booter.bootable?(component) || component.file_exists?(load_paths)
634
671
  booter.boot_dependency(component) unless finalized?
635
672
 
@@ -637,9 +674,11 @@ module Dry
637
674
  register(component.identifier) { component.instance }
638
675
  end
639
676
  elsif !default_namespace_fallback
640
- load_local_component(component.prepend(config.default_namespace), true)
677
+ load_local_component(component.prepend(config.default_namespace), true, &block)
641
678
  elsif manual_registrar.file_exists?(component)
642
679
  manual_registrar.(component)
680
+ elsif block_given?
681
+ yield
643
682
  else
644
683
  raise ComponentLoadError, component
645
684
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module System
3
5
  # Error raised when the container tries to load a component with missing
@@ -15,7 +17,13 @@ module Dry
15
17
  # @api public
16
18
  ComponentFileMismatchError = Class.new(StandardError) do
17
19
  def initialize(component)
18
- super("Boot file for component #{component.identifier.inspect} not found. Container boot files under #{component.boot_path}: #{component.container_boot_files.inspect}")
20
+ path = component.boot_path
21
+ files = component.container_boot_files
22
+
23
+ super(<<-STR)
24
+ Boot file for component #{component.identifier.inspect} not found.
25
+ Container boot files under #{path}: #{files.inspect}")
26
+ STR
19
27
  end
20
28
  end
21
29
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module System
3
5
  # Default importer implementation
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'concurrent/map'
2
4
 
3
5
  require 'dry/system/settings'
@@ -124,7 +126,7 @@ module Dry
124
126
 
125
127
  # @api private
126
128
  def method_missing(meth, *args, &block)
127
- if target.key?(meth)
129
+ if target.registered?(meth)
128
130
  target[meth]
129
131
  elsif container.key?(meth)
130
132
  container[meth]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/inflector'
2
4
 
3
5
  module Dry
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dry
2
4
  module System
3
5
  class MagicCommentsParser
@@ -6,7 +8,7 @@ module Dry
6
8
 
7
9
  COERCIONS = {
8
10
  'true' => true,
9
- 'false' => false,
11
+ 'false' => false
10
12
  }.freeze
11
13
 
12
14
  def self.call(file_name)
@@ -21,8 +23,6 @@ module Dry
21
23
  end
22
24
  end
23
25
 
24
- private
25
-
26
26
  def self.coerce(value)
27
27
  COERCIONS.fetch(value) { value }
28
28
  end