dry-system 0.18.0 → 0.19.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 +103 -0
- data/LICENSE +1 -1
- data/README.md +1 -1
- data/dry-system.gemspec +3 -4
- data/lib/dry/system/auto_registrar.rb +16 -58
- data/lib/dry/system/booter.rb +48 -28
- data/lib/dry/system/component.rb +56 -94
- data/lib/dry/system/component_dir.rb +128 -0
- data/lib/dry/system/config/component_dir.rb +202 -0
- data/lib/dry/system/config/component_dirs.rb +184 -0
- data/lib/dry/system/container.rb +78 -143
- data/lib/dry/system/errors.rb +21 -12
- data/lib/dry/system/identifier.rb +157 -0
- data/lib/dry/system/loader/autoloading.rb +26 -0
- data/lib/dry/system/loader.rb +40 -41
- data/lib/dry/system/version.rb +1 -1
- metadata +17 -27
- data/lib/dry/system/auto_registrar/configuration.rb +0 -43
data/lib/dry/system/container.rb
CHANGED
@@ -11,7 +11,6 @@ require "dry/core/deprecations"
|
|
11
11
|
|
12
12
|
require "dry/system"
|
13
13
|
require "dry/system/errors"
|
14
|
-
require "dry/system/loader"
|
15
14
|
require "dry/system/booter"
|
16
15
|
require "dry/system/auto_registrar"
|
17
16
|
require "dry/system/manual_registrar"
|
@@ -20,6 +19,9 @@ require "dry/system/component"
|
|
20
19
|
require "dry/system/constants"
|
21
20
|
require "dry/system/plugins"
|
22
21
|
|
22
|
+
require_relative "component_dir"
|
23
|
+
require_relative "config/component_dirs"
|
24
|
+
|
23
25
|
module Dry
|
24
26
|
module System
|
25
27
|
# Abstract container class to inherit from
|
@@ -61,7 +63,7 @@ module Dry
|
|
61
63
|
# end
|
62
64
|
#
|
63
65
|
# # this will configure $LOAD_PATH to include your `lib` dir
|
64
|
-
#
|
66
|
+
# add_dirs_to_load_paths!('lib')
|
65
67
|
# end
|
66
68
|
#
|
67
69
|
# @api public
|
@@ -71,14 +73,12 @@ module Dry
|
|
71
73
|
extend Dry::System::Plugins
|
72
74
|
|
73
75
|
setting :name
|
74
|
-
setting :default_namespace
|
75
76
|
setting(:root, Pathname.pwd.freeze) { |path| Pathname(path) }
|
76
77
|
setting :system_dir, "system"
|
77
78
|
setting :bootable_dirs, ["system/boot"]
|
78
79
|
setting :registrations_dir, "container"
|
79
|
-
setting :
|
80
|
+
setting :component_dirs, Config::ComponentDirs.new, cloneable: true
|
80
81
|
setting :inflector, Dry::Inflector.new
|
81
|
-
setting :loader, Dry::System::Loader
|
82
82
|
setting :booter, Dry::System::Booter
|
83
83
|
setting :auto_registrar, Dry::System::AutoRegistrar
|
84
84
|
setting :manual_registrar, Dry::System::ManualRegistrar
|
@@ -125,7 +125,6 @@ module Dry
|
|
125
125
|
def configure(&block)
|
126
126
|
hooks[:before_configure].each { |hook| instance_eval(&hook) }
|
127
127
|
super(&block)
|
128
|
-
load_paths!(config.system_dir)
|
129
128
|
hooks[:after_configure].each { |hook| instance_eval(&hook) }
|
130
129
|
self
|
131
130
|
end
|
@@ -384,7 +383,7 @@ module Dry
|
|
384
383
|
self
|
385
384
|
end
|
386
385
|
|
387
|
-
#
|
386
|
+
# Adds the directories (relative to the container's root) to the Ruby load path
|
388
387
|
#
|
389
388
|
# @example
|
390
389
|
# class MyApp < Dry::System::Container
|
@@ -392,7 +391,7 @@ module Dry
|
|
392
391
|
# # ...
|
393
392
|
# end
|
394
393
|
#
|
395
|
-
#
|
394
|
+
# add_to_load_path!('lib')
|
396
395
|
# end
|
397
396
|
#
|
398
397
|
# @param [Array<String>] dirs
|
@@ -400,12 +399,9 @@ module Dry
|
|
400
399
|
# @return [self]
|
401
400
|
#
|
402
401
|
# @api public
|
403
|
-
def
|
404
|
-
dirs.map(&root.method(:join)).each do |path|
|
405
|
-
|
406
|
-
|
407
|
-
load_paths << path
|
408
|
-
$LOAD_PATH.unshift(path.to_s)
|
402
|
+
def add_to_load_path!(*dirs)
|
403
|
+
dirs.reverse.map(&root.method(:join)).each do |path|
|
404
|
+
$LOAD_PATH.prepend(path.to_s) unless $LOAD_PATH.include?(path.to_s)
|
409
405
|
end
|
410
406
|
self
|
411
407
|
end
|
@@ -416,47 +412,6 @@ module Dry
|
|
416
412
|
self
|
417
413
|
end
|
418
414
|
|
419
|
-
# Auto-registers components from the provided directory
|
420
|
-
#
|
421
|
-
# Typically you want to configure auto_register directories, and it will
|
422
|
-
# work automatically. Use this method in cases where you want to have an
|
423
|
-
# explicit way where some components are auto-registered, or if you want
|
424
|
-
# to exclude some components from being auto-registered
|
425
|
-
#
|
426
|
-
# @example
|
427
|
-
# class MyApp < Dry::System::Container
|
428
|
-
# configure do |config|
|
429
|
-
# # ...
|
430
|
-
# end
|
431
|
-
#
|
432
|
-
# # with a dir
|
433
|
-
# auto_register!('lib/core')
|
434
|
-
#
|
435
|
-
# # with a dir and a custom registration block
|
436
|
-
# auto_register!('lib/core') do |config|
|
437
|
-
# config.instance do |component|
|
438
|
-
# # custom way of initializing a component
|
439
|
-
# end
|
440
|
-
#
|
441
|
-
# config.exclude do |component|
|
442
|
-
# # return true to exclude component from auto-registration
|
443
|
-
# end
|
444
|
-
# end
|
445
|
-
# end
|
446
|
-
#
|
447
|
-
# @param [String] dir The dir name relative to the root dir
|
448
|
-
#
|
449
|
-
# @yield AutoRegistrar::Configuration
|
450
|
-
# @see AutoRegistrar::Configuration
|
451
|
-
#
|
452
|
-
# @return [self]
|
453
|
-
#
|
454
|
-
# @api public
|
455
|
-
def auto_register!(dir, &block)
|
456
|
-
auto_registrar.(dir, &block)
|
457
|
-
self
|
458
|
-
end
|
459
|
-
|
460
415
|
# Builds injector for this container
|
461
416
|
#
|
462
417
|
# An injector is a useful mixin which injects dependencies into
|
@@ -525,8 +480,8 @@ module Dry
|
|
525
480
|
end
|
526
481
|
|
527
482
|
# @api public
|
528
|
-
def resolve(key
|
529
|
-
load_component(key
|
483
|
+
def resolve(key)
|
484
|
+
load_component(key) unless finalized?
|
530
485
|
|
531
486
|
super
|
532
487
|
end
|
@@ -557,8 +512,8 @@ module Dry
|
|
557
512
|
end
|
558
513
|
|
559
514
|
# @api private
|
560
|
-
def
|
561
|
-
|
515
|
+
def component_dirs
|
516
|
+
config.component_dirs.to_a.map { |dir| ComponentDir.new(config: dir, container: self) }
|
562
517
|
end
|
563
518
|
|
564
519
|
# @api private
|
@@ -594,67 +549,6 @@ module Dry
|
|
594
549
|
@importer ||= config.importer.new(self)
|
595
550
|
end
|
596
551
|
|
597
|
-
# @api private
|
598
|
-
def component(identifier, **options)
|
599
|
-
if (component = booter.components.detect { |c| c.identifier == identifier })
|
600
|
-
component
|
601
|
-
else
|
602
|
-
Component.new(
|
603
|
-
identifier,
|
604
|
-
loader: config.loader,
|
605
|
-
namespace: config.default_namespace,
|
606
|
-
separator: config.namespace_separator,
|
607
|
-
inflector: config.inflector,
|
608
|
-
**options
|
609
|
-
)
|
610
|
-
end
|
611
|
-
end
|
612
|
-
|
613
|
-
# @api private
|
614
|
-
def require_component(component)
|
615
|
-
return if registered?(component.identifier)
|
616
|
-
|
617
|
-
raise FileNotFoundError, component unless component.file_exists?(load_paths)
|
618
|
-
|
619
|
-
require_path(component.path)
|
620
|
-
|
621
|
-
yield
|
622
|
-
end
|
623
|
-
|
624
|
-
# Allows subclasses to use a different strategy for required files.
|
625
|
-
#
|
626
|
-
# E.g. apps that use `ActiveSupport::Dependencies::Loadable#require_dependency`
|
627
|
-
# will override this method to allow container managed dependencies to be reloaded
|
628
|
-
# for non-finalized containers.
|
629
|
-
#
|
630
|
-
# @api private
|
631
|
-
def require_path(path)
|
632
|
-
require path
|
633
|
-
end
|
634
|
-
|
635
|
-
# @api private
|
636
|
-
def load_component(key, &block)
|
637
|
-
return self if registered?(key)
|
638
|
-
|
639
|
-
component(key).tap do |component|
|
640
|
-
if component.bootable?
|
641
|
-
booter.start(component)
|
642
|
-
else
|
643
|
-
root_key = component.root_key
|
644
|
-
|
645
|
-
if (root_bootable = component(root_key)).bootable?
|
646
|
-
booter.start(root_bootable)
|
647
|
-
elsif importer.key?(root_key)
|
648
|
-
load_imported_component(component.namespaced(root_key))
|
649
|
-
end
|
650
|
-
|
651
|
-
load_local_component(component, &block) unless registered?(key)
|
652
|
-
end
|
653
|
-
end
|
654
|
-
|
655
|
-
self
|
656
|
-
end
|
657
|
-
|
658
552
|
# @api private
|
659
553
|
def after(event, &block)
|
660
554
|
hooks[:"after_#{event}"] << block
|
@@ -671,46 +565,87 @@ module Dry
|
|
671
565
|
|
672
566
|
# @api private
|
673
567
|
def inherited(klass)
|
674
|
-
new_hooks = Container.hooks.dup
|
675
|
-
|
676
568
|
hooks.each do |event, blocks|
|
677
|
-
|
678
|
-
new_hooks[event].concat(klass.hooks[event])
|
569
|
+
klass.hooks[event].concat blocks.dup
|
679
570
|
end
|
680
571
|
|
681
|
-
klass.instance_variable_set(:@hooks, new_hooks)
|
682
572
|
klass.instance_variable_set(:@__finalized__, false)
|
573
|
+
|
683
574
|
super
|
684
575
|
end
|
685
576
|
|
686
|
-
|
577
|
+
protected
|
687
578
|
|
688
579
|
# @api private
|
689
|
-
def
|
690
|
-
|
691
|
-
booter.boot_dependency(component) unless finalized?
|
580
|
+
def load_component(key)
|
581
|
+
return self if registered?(key)
|
692
582
|
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
583
|
+
component = component(key)
|
584
|
+
|
585
|
+
if component.bootable?
|
586
|
+
booter.start(component)
|
587
|
+
return self
|
588
|
+
end
|
589
|
+
|
590
|
+
booter.boot_dependency(component)
|
591
|
+
return self if registered?(key)
|
592
|
+
|
593
|
+
if component.file_exists?
|
594
|
+
load_local_component(component)
|
698
595
|
elsif manual_registrar.file_exists?(component)
|
699
596
|
manual_registrar.(component)
|
700
|
-
elsif
|
701
|
-
|
702
|
-
else
|
703
|
-
raise ComponentLoadError, component
|
597
|
+
elsif importer.key?(component.identifier.root_key)
|
598
|
+
load_imported_component(component.identifier)
|
704
599
|
end
|
600
|
+
|
601
|
+
self
|
705
602
|
end
|
706
603
|
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
604
|
+
private
|
605
|
+
|
606
|
+
def load_local_component(component)
|
607
|
+
if component.auto_register?
|
608
|
+
register(component.identifier, memoize: component.memoize?) { component.instance }
|
609
|
+
end
|
610
|
+
end
|
611
|
+
|
612
|
+
def load_imported_component(identifier)
|
613
|
+
import_namespace = identifier.root_key
|
614
|
+
|
615
|
+
container = importer[import_namespace]
|
616
|
+
|
617
|
+
container.load_component(identifier.dequalified(import_namespace).key)
|
618
|
+
|
619
|
+
importer.(import_namespace, container)
|
620
|
+
end
|
621
|
+
|
622
|
+
def component(identifier)
|
623
|
+
if (bootable_component = booter.find_component(identifier))
|
624
|
+
return bootable_component
|
625
|
+
end
|
626
|
+
|
627
|
+
# Find the first matching component from within the configured component dirs.
|
628
|
+
# If no matching component is found, return a plain component instance with no
|
629
|
+
# associated file path. This fallback is important because the component may
|
630
|
+
# still be loadable via the manual registrar or an imported container.
|
631
|
+
component_dirs.detect { |dir|
|
632
|
+
if (component = dir.component_for_identifier(identifier))
|
633
|
+
break component
|
634
|
+
end
|
635
|
+
} || Component.new(identifier)
|
712
636
|
end
|
713
637
|
end
|
638
|
+
|
639
|
+
# Default hooks
|
640
|
+
after :configure do
|
641
|
+
# Add appropriately configured component dirs to the load path
|
642
|
+
#
|
643
|
+
# Do this in a single pass to preserve ordering (i.e. earliest dirs win)
|
644
|
+
paths = config.component_dirs.to_a.each_with_object([]) { |dir, arr|
|
645
|
+
arr << dir.path if dir.add_to_load_path
|
646
|
+
}
|
647
|
+
add_to_load_path!(*paths)
|
648
|
+
end
|
714
649
|
end
|
715
650
|
end
|
716
651
|
end
|
data/lib/dry/system/errors.rb
CHANGED
@@ -2,13 +2,22 @@
|
|
2
2
|
|
3
3
|
module Dry
|
4
4
|
module System
|
5
|
+
# Error raised when a component dir is added to configuration more than once
|
6
|
+
#
|
7
|
+
# @api public
|
8
|
+
ComponentDirAlreadyAddedError = Class.new(StandardError) do
|
9
|
+
def initialize(dir)
|
10
|
+
super("Component directory #{dir.inspect} already added")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
5
14
|
# Error raised when the container tries to load a component with missing
|
6
15
|
# file
|
7
16
|
#
|
8
17
|
# @api public
|
9
18
|
FileNotFoundError = Class.new(StandardError) do
|
10
19
|
def initialize(component)
|
11
|
-
super("could not resolve require file for #{component.identifier}")
|
20
|
+
super("could not resolve require file for component '#{component.identifier}'")
|
12
21
|
end
|
13
22
|
end
|
14
23
|
|
@@ -18,20 +27,11 @@ module Dry
|
|
18
27
|
ComponentFileMismatchError = Class.new(StandardError) do
|
19
28
|
def initialize(component)
|
20
29
|
super(<<-STR)
|
21
|
-
Bootable component #{component.identifier
|
30
|
+
Bootable component '#{component.identifier}' not found
|
22
31
|
STR
|
23
32
|
end
|
24
33
|
end
|
25
34
|
|
26
|
-
# Error raised when a resolved component couldn't be found
|
27
|
-
#
|
28
|
-
# @api public
|
29
|
-
ComponentLoadError = Class.new(StandardError) do
|
30
|
-
def initialize(component)
|
31
|
-
super("could not load component #{component.inspect}")
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
35
|
# Error raised when resolved component couldn't be loaded
|
36
36
|
#
|
37
37
|
# @api public
|
@@ -81,8 +81,17 @@ module Dry
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
-
|
84
|
+
# Error raised when a configured component directory could not be found
|
85
|
+
#
|
86
|
+
# @api public
|
87
|
+
ComponentDirNotFoundError = Class.new(StandardError) do
|
88
|
+
def initialize(dir)
|
89
|
+
super("Component dir '#{dir}' not found")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
85
93
|
DuplicatedComponentKeyError = Class.new(ArgumentError)
|
94
|
+
|
86
95
|
InvalidSettingsError = Class.new(ArgumentError) do
|
87
96
|
# @api private
|
88
97
|
def initialize(attributes)
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/equalizer"
|
4
|
+
require_relative "constants"
|
5
|
+
|
6
|
+
module Dry
|
7
|
+
module System
|
8
|
+
# An identifier representing a component to be registered.
|
9
|
+
#
|
10
|
+
# Components are eventually registered in the container using plain string
|
11
|
+
# identifiers, available as the `identifier` or `key` attribute here. Additional
|
12
|
+
# methods are provided to make it easier to evaluate or manipulate these identifiers.
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
class Identifier
|
16
|
+
include Dry::Equalizer(:identifier, :namespace, :separator)
|
17
|
+
|
18
|
+
# @return [String] the identifier string
|
19
|
+
# @api public
|
20
|
+
attr_reader :identifier
|
21
|
+
|
22
|
+
# @return [String, nil] the namespace for the component
|
23
|
+
# @api public
|
24
|
+
attr_reader :namespace
|
25
|
+
|
26
|
+
# @return [String] the configured namespace separator
|
27
|
+
# @api public
|
28
|
+
attr_reader :separator
|
29
|
+
|
30
|
+
# @api private
|
31
|
+
def initialize(identifier, namespace: nil, separator: DEFAULT_SEPARATOR)
|
32
|
+
@identifier = identifier.to_s
|
33
|
+
@namespace = namespace
|
34
|
+
@separator = separator
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!method key
|
38
|
+
# Returns the identifier string
|
39
|
+
#
|
40
|
+
# @return [String]
|
41
|
+
# @see #identifier
|
42
|
+
# @api public
|
43
|
+
alias_method :key, :identifier
|
44
|
+
|
45
|
+
# @!method to_s
|
46
|
+
# Returns the identifier string
|
47
|
+
#
|
48
|
+
# @return [String]
|
49
|
+
# @see #identifier
|
50
|
+
# @api public
|
51
|
+
alias_method :to_s, :identifier
|
52
|
+
|
53
|
+
# Returns the root namespace segment of the identifier string, as a symbol
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# identifier.key # => "articles.operations.create"
|
57
|
+
# identifier.root_key # => :articles
|
58
|
+
#
|
59
|
+
# @return [Symbol] the root key
|
60
|
+
# @api public
|
61
|
+
def root_key
|
62
|
+
segments.first.to_sym
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns a path-delimited representation of the identifier, with the namespace
|
66
|
+
# incorporated. This path is intended for usage when requiring the component's
|
67
|
+
# source file.
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# identifier.key # => "articles.operations.create"
|
71
|
+
# identifier.namespace # => "admin"
|
72
|
+
#
|
73
|
+
# identifier.path # => "admin/articles/operations/create"
|
74
|
+
#
|
75
|
+
# @return [String] the path
|
76
|
+
# @api public
|
77
|
+
def path
|
78
|
+
@require_path ||= identifier.gsub(separator, PATH_SEPARATOR).yield_self { |path|
|
79
|
+
if namespace
|
80
|
+
namespace_path = namespace.to_s.gsub(separator, PATH_SEPARATOR)
|
81
|
+
"#{namespace_path}#{PATH_SEPARATOR}#{path}"
|
82
|
+
else
|
83
|
+
path
|
84
|
+
end
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns true if the given namespace prefix is part of the identifier's leading
|
89
|
+
# namespaces
|
90
|
+
#
|
91
|
+
# @example
|
92
|
+
# identifier.key # => "articles.operations.create"
|
93
|
+
#
|
94
|
+
# identifier.start_with?("articles.operations") # => true
|
95
|
+
# identifier.start_with?("articles") # => true
|
96
|
+
# identifier.start_with?("article") # => false
|
97
|
+
#
|
98
|
+
# @param leading_namespaces [String] the one or more leading namespaces to check
|
99
|
+
# @return [Boolean]
|
100
|
+
# @api public
|
101
|
+
def start_with?(leading_namespaces)
|
102
|
+
identifier.start_with?("#{leading_namespaces}#{separator}")
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns a copy of the identifier with the given leading namespaces removed from
|
106
|
+
# the identifier string.
|
107
|
+
#
|
108
|
+
# Additional options may be provided, which are passed to #initialize when
|
109
|
+
# constructing the new copy of the identifier
|
110
|
+
#
|
111
|
+
# @param leading_namespace [String] the one or more leading namespaces to remove
|
112
|
+
# @param options [Hash] additional options for initialization
|
113
|
+
#
|
114
|
+
# @return [Dry::System::Identifier] the copy of the identifier
|
115
|
+
#
|
116
|
+
# @see #initialize
|
117
|
+
# @api private
|
118
|
+
def dequalified(leading_namespaces, **options)
|
119
|
+
new_identifier = identifier.gsub(
|
120
|
+
/^#{Regexp.escape(leading_namespaces)}#{Regexp.escape(separator)}/,
|
121
|
+
EMPTY_STRING
|
122
|
+
)
|
123
|
+
|
124
|
+
return self if new_identifier == identifier
|
125
|
+
|
126
|
+
self.class.new(
|
127
|
+
new_identifier,
|
128
|
+
namespace: namespace,
|
129
|
+
separator: separator,
|
130
|
+
**options
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns a copy of the identifier with the given options applied
|
135
|
+
#
|
136
|
+
# @param namespace [String, nil] a new namespace to be used
|
137
|
+
#
|
138
|
+
# @return [Dry::System::Identifier] the copy of the identifier
|
139
|
+
#
|
140
|
+
# @see #initialize
|
141
|
+
# @api private
|
142
|
+
def with(namespace:)
|
143
|
+
self.class.new(
|
144
|
+
identifier,
|
145
|
+
namespace: namespace,
|
146
|
+
separator: separator
|
147
|
+
)
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def segments
|
153
|
+
@segments ||= identifier.split(separator)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../loader"
|
4
|
+
|
5
|
+
module Dry
|
6
|
+
module System
|
7
|
+
class Loader
|
8
|
+
# Component loader for autoloading-enabled applications
|
9
|
+
#
|
10
|
+
# This behaves like the default loader, except instead of requiring the given path,
|
11
|
+
# it loads the respective constant, allowing the autoloader to load the
|
12
|
+
# corresponding file per its own configuration.
|
13
|
+
#
|
14
|
+
# @see Loader
|
15
|
+
# @api public
|
16
|
+
class Autoloading < Loader
|
17
|
+
class << self
|
18
|
+
def require!(component)
|
19
|
+
constant(component)
|
20
|
+
self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/dry/system/loader.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "dry/inflector"
|
4
|
-
|
5
3
|
module Dry
|
6
4
|
module System
|
7
5
|
# Default component loader implementation
|
@@ -25,52 +23,53 @@ module Dry
|
|
25
23
|
#
|
26
24
|
# @api public
|
27
25
|
class Loader
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
class << self
|
27
|
+
# Requires the component's source file
|
28
|
+
#
|
29
|
+
# @api public
|
30
|
+
def require!(component)
|
31
|
+
require(component.path) if component.file_exists?
|
32
|
+
self
|
33
|
+
end
|
31
34
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
+
# Returns an instance of the component
|
36
|
+
#
|
37
|
+
# Provided optional args are passed to object's constructor
|
38
|
+
#
|
39
|
+
# @param [Array] args Optional constructor args
|
40
|
+
#
|
41
|
+
# @return [Object]
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
def call(component, *args)
|
45
|
+
require!(component)
|
35
46
|
|
36
|
-
|
37
|
-
def initialize(path, inflector = Dry::Inflector.new)
|
38
|
-
@path = path
|
39
|
-
@inflector = inflector
|
40
|
-
end
|
47
|
+
constant = self.constant(component)
|
41
48
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
#
|
48
|
-
# @return [Object]
|
49
|
-
#
|
50
|
-
# @api public
|
51
|
-
def call(*args)
|
52
|
-
if singleton?(constant)
|
53
|
-
constant.instance(*args)
|
54
|
-
else
|
55
|
-
constant.new(*args)
|
49
|
+
if singleton?(constant)
|
50
|
+
constant.instance(*args)
|
51
|
+
else
|
52
|
+
constant.new(*args)
|
53
|
+
end
|
56
54
|
end
|
57
|
-
|
58
|
-
ruby2_keywords(:call) if respond_to?(:ruby2_keywords, true)
|
55
|
+
ruby2_keywords(:call) if respond_to?(:ruby2_keywords, true)
|
59
56
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
57
|
+
# Returns the component's class constant
|
58
|
+
#
|
59
|
+
# @return [Class]
|
60
|
+
#
|
61
|
+
# @api public
|
62
|
+
def constant(component)
|
63
|
+
inflector = component.inflector
|
64
|
+
|
65
|
+
inflector.constantize(inflector.camelize(component.path))
|
66
|
+
end
|
68
67
|
|
69
|
-
|
68
|
+
private
|
70
69
|
|
71
|
-
|
72
|
-
|
73
|
-
|
70
|
+
def singleton?(constant)
|
71
|
+
constant.respond_to?(:instance) && !constant.respond_to?(:new)
|
72
|
+
end
|
74
73
|
end
|
75
74
|
end
|
76
75
|
end
|