dry-system 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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