dry-system 0.7.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/lib/dry/system.rb +24 -0
  4. data/lib/dry/system/booter.rb +75 -43
  5. data/lib/dry/system/booter/component_registry.rb +37 -0
  6. data/lib/dry/system/component.rb +9 -4
  7. data/lib/dry/system/components.rb +6 -0
  8. data/lib/dry/system/components/bootable.rb +307 -0
  9. data/lib/dry/system/components/config.rb +33 -0
  10. data/lib/dry/system/constants.rb +2 -0
  11. data/lib/dry/system/container.rb +69 -22
  12. data/lib/dry/system/errors.rb +2 -2
  13. data/lib/dry/system/lifecycle.rb +40 -7
  14. data/lib/dry/system/provider.rb +46 -0
  15. data/lib/dry/system/provider_registry.rb +25 -0
  16. data/lib/dry/system/settings.rb +59 -0
  17. data/lib/dry/system/settings/file_loader.rb +28 -0
  18. data/lib/dry/system/settings/file_parser.rb +49 -0
  19. data/lib/dry/system/system_components/settings.rb +9 -0
  20. data/lib/dry/system/version.rb +1 -1
  21. metadata +27 -144
  22. data/.gitignore +0 -39
  23. data/.rspec +0 -3
  24. data/.travis.yml +0 -22
  25. data/.yardopts +0 -5
  26. data/CONTRIBUTING.md +0 -29
  27. data/Gemfile +0 -9
  28. data/Rakefile +0 -12
  29. data/dry-system.gemspec +0 -31
  30. data/examples/custom_configuration_auto_register/Gemfile +0 -5
  31. data/examples/custom_configuration_auto_register/lib/entities/user.rb +0 -7
  32. data/examples/custom_configuration_auto_register/lib/user_repo.rb +0 -5
  33. data/examples/custom_configuration_auto_register/run.rb +0 -8
  34. data/examples/custom_configuration_auto_register/system/boot/persistence.rb +0 -13
  35. data/examples/custom_configuration_auto_register/system/container.rb +0 -16
  36. data/examples/custom_configuration_auto_register/system/import.rb +0 -3
  37. data/examples/standalone/Gemfile +0 -5
  38. data/examples/standalone/lib/user_repo.rb +0 -5
  39. data/examples/standalone/run.rb +0 -8
  40. data/examples/standalone/system/boot/persistence.rb +0 -13
  41. data/examples/standalone/system/container.rb +0 -9
  42. data/examples/standalone/system/import.rb +0 -3
  43. data/spec/fixtures/components/bar.rb +0 -5
  44. data/spec/fixtures/components/bar/baz.rb +0 -4
  45. data/spec/fixtures/components/foo.rb +0 -2
  46. data/spec/fixtures/components/no_register.rb +0 -4
  47. data/spec/fixtures/import_test/config/application.yml +0 -2
  48. data/spec/fixtures/import_test/lib/test/bar.rb +0 -4
  49. data/spec/fixtures/import_test/lib/test/foo.rb +0 -5
  50. data/spec/fixtures/import_test/system/boot/bar.rb +0 -11
  51. data/spec/fixtures/lazytest/config/application.yml +0 -2
  52. data/spec/fixtures/lazytest/lib/test/dep.rb +0 -4
  53. data/spec/fixtures/lazytest/lib/test/foo.rb +0 -5
  54. data/spec/fixtures/lazytest/lib/test/models.rb +0 -4
  55. data/spec/fixtures/lazytest/lib/test/models/book.rb +0 -6
  56. data/spec/fixtures/lazytest/lib/test/models/user.rb +0 -6
  57. data/spec/fixtures/lazytest/system/boot/bar.rb +0 -15
  58. data/spec/fixtures/magic_comments/comments.rb +0 -17
  59. data/spec/fixtures/manual_registration/container/foo.rb +0 -6
  60. data/spec/fixtures/manual_registration/lib/test/foo.rb +0 -9
  61. data/spec/fixtures/multiple_namespaced_components/multiple/level/baz.rb +0 -7
  62. data/spec/fixtures/multiple_namespaced_components/multiple/level/foz.rb +0 -6
  63. data/spec/fixtures/namespaced_components/namespaced/bar.rb +0 -5
  64. data/spec/fixtures/namespaced_components/namespaced/foo.rb +0 -4
  65. data/spec/fixtures/other/config/boot/bar.rb +0 -11
  66. data/spec/fixtures/other/config/boot/hell.rb +0 -3
  67. data/spec/fixtures/other/lib/test/dep.rb +0 -4
  68. data/spec/fixtures/other/lib/test/foo.rb +0 -5
  69. data/spec/fixtures/other/lib/test/models.rb +0 -4
  70. data/spec/fixtures/other/lib/test/models/book.rb +0 -6
  71. data/spec/fixtures/other/lib/test/models/user.rb +0 -6
  72. data/spec/fixtures/stubbing/lib/test/car.rb +0 -7
  73. data/spec/fixtures/stubbing/system/boot/db.rb +0 -8
  74. data/spec/fixtures/test/config/application.yml +0 -2
  75. data/spec/fixtures/test/config/subapp.yml +0 -2
  76. data/spec/fixtures/test/lib/test/dep.rb +0 -4
  77. data/spec/fixtures/test/lib/test/foo.rb +0 -5
  78. data/spec/fixtures/test/lib/test/models.rb +0 -4
  79. data/spec/fixtures/test/lib/test/models/book.rb +0 -6
  80. data/spec/fixtures/test/lib/test/models/user.rb +0 -6
  81. data/spec/fixtures/test/lib/test/singleton_dep.rb +0 -7
  82. data/spec/fixtures/test/log/.gitkeep +0 -0
  83. data/spec/fixtures/test/system/boot/bar.rb +0 -11
  84. data/spec/fixtures/test/system/boot/client.rb +0 -7
  85. data/spec/fixtures/test/system/boot/db.rb +0 -1
  86. data/spec/fixtures/test/system/boot/hell.rb +0 -3
  87. data/spec/fixtures/test/system/boot/logger.rb +0 -5
  88. data/spec/fixtures/umbrella/system/boot/db.rb +0 -10
  89. data/spec/integration/boot_spec.rb +0 -18
  90. data/spec/integration/container/lazy_loading/manual_registration_spec.rb +0 -18
  91. data/spec/integration/import_spec.rb +0 -81
  92. data/spec/spec_helper.rb +0 -48
  93. data/spec/unit/auto_registrar/configuration_spec.rb +0 -26
  94. data/spec/unit/component_spec.rb +0 -121
  95. data/spec/unit/container/auto_register_spec.rb +0 -113
  96. data/spec/unit/container/config_spec.rb +0 -38
  97. data/spec/unit/container/finalize_spec.rb +0 -97
  98. data/spec/unit/container/import_spec.rb +0 -53
  99. data/spec/unit/container/injector_spec.rb +0 -29
  100. data/spec/unit/container_spec.rb +0 -244
  101. data/spec/unit/loader_spec.rb +0 -64
  102. data/spec/unit/magic_comments_parser_spec.rb +0 -41
@@ -0,0 +1,33 @@
1
+ module Dry
2
+ module System
3
+ module Components
4
+ class Config
5
+ def self.new(&block)
6
+ config = super
7
+ yield(config) if block_given?
8
+ config
9
+ end
10
+
11
+ def initialize
12
+ @settings = {}
13
+ end
14
+
15
+ def to_hash
16
+ @settings
17
+ end
18
+
19
+ private
20
+
21
+ def method_missing(meth, value = nil)
22
+ if meth.to_s.end_with?('=')
23
+ @settings[meth.to_s.gsub('=', '').to_sym] = value
24
+ elsif @settings.key?(meth)
25
+ @settings[meth]
26
+ else
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -6,5 +6,7 @@ module Dry
6
6
  PATH_SEPARATOR = '/'.freeze
7
7
  DEFAULT_SEPARATOR = '.'.freeze
8
8
  WORD_REGEX = /\w+/.freeze
9
+
10
+ DuplicatedComponentKeyError = Class.new(ArgumentError)
9
11
  end
10
12
  end
@@ -6,6 +6,7 @@ require 'dry-container'
6
6
 
7
7
  require 'dry/core/deprecations'
8
8
 
9
+ require 'dry/system'
9
10
  require 'dry/system/errors'
10
11
  require 'dry/system/loader'
11
12
  require 'dry/system/booter'
@@ -77,6 +78,7 @@ module Dry
77
78
  setting :auto_registrar, Dry::System::AutoRegistrar
78
79
  setting :manual_registrar, Dry::System::ManualRegistrar
79
80
  setting :importer, Dry::System::Importer
81
+ setting(:components, {}, reader: true) { |v| v.dup }
80
82
 
81
83
  class << self
82
84
  extend Dry::Core::Deprecations['Dry::System::Container']
@@ -154,7 +156,7 @@ module Dry
154
156
  # # system/boot/db.rb
155
157
  # #
156
158
  # # Simple component registration
157
- # MyApp.finalize(:db) do |container|
159
+ # MyApp.boot(:db) do |container|
158
160
  # require 'db'
159
161
  #
160
162
  # container.register(:db, DB.new)
@@ -163,7 +165,7 @@ module Dry
163
165
  # # system/boot/db.rb
164
166
  # #
165
167
  # # Component registration with lifecycle triggers
166
- # MyApp.finalize(:db) do |container|
168
+ # MyApp.boot(:db) do |container|
167
169
  # init do
168
170
  # require 'db'
169
171
  # DB.configure(ENV['DB_URL'])
@@ -182,7 +184,7 @@ module Dry
182
184
  # # system/boot/db.rb
183
185
  # #
184
186
  # # Component registration which uses another bootable component
185
- # MyApp.finalize(:db) do |container|
187
+ # MyApp.boot(:db) do |container|
186
188
  # use :logger
187
189
  #
188
190
  # start do
@@ -209,9 +211,41 @@ module Dry
209
211
  # @return [self]
210
212
  #
211
213
  # @api public
212
- def finalize(name, &block)
213
- booter[name] = [self, block]
214
+ def boot(name, opts = {}, &block)
215
+ if components.key?(name)
216
+ raise DuplicatedComponentKeyError, "Bootable component #{name.inspect} was already registered"
217
+ end
218
+
219
+ component =
220
+ if opts[:from]
221
+ boot_external(name, opts, &block)
222
+ else
223
+ boot_local(name, opts, &block)
224
+ end
214
225
  self
226
+
227
+ components[name] = component
228
+ end
229
+ deprecate :finalize, :boot
230
+
231
+ # @api private
232
+ def boot_external(identifier, from:, key: nil, namespace: nil, &block)
233
+ component = System.providers[from].component(
234
+ key || identifier, key: identifier, namespace: namespace, finalize: block, container: self
235
+ )
236
+
237
+ booter.register_component(component)
238
+
239
+ component
240
+ end
241
+
242
+ # @api private
243
+ def boot_local(name, namespace: nil, &block)
244
+ component = Components::Bootable.new(name, container: self, namespace: namespace, &block)
245
+
246
+ booter.register_component(component)
247
+
248
+ component
215
249
  end
216
250
 
217
251
  # Return if a container was finalized
@@ -283,7 +317,6 @@ module Dry
283
317
  booter.start(name)
284
318
  self
285
319
  end
286
- deprecate :boot!, :start
287
320
 
288
321
  # Boots a specific component but calls only `init` lifecycle trigger
289
322
  #
@@ -302,7 +335,6 @@ module Dry
302
335
  booter.init(name)
303
336
  self
304
337
  end
305
- deprecate :boot, :init
306
338
 
307
339
  # Sets load paths relative to the container's root dir
308
340
  #
@@ -457,7 +489,12 @@ module Dry
457
489
 
458
490
  # @api private
459
491
  def booter
460
- @booter ||= config.booter.new(root.join("#{config.system_dir}/boot"))
492
+ @booter ||= config.booter.new(boot_path)
493
+ end
494
+
495
+ # @api private
496
+ def boot_path
497
+ root.join("#{config.system_dir}/boot")
461
498
  end
462
499
 
463
500
  # @api private
@@ -476,14 +513,18 @@ module Dry
476
513
  end
477
514
 
478
515
  # @api private
479
- def component(key, **options)
480
- Component.new(
481
- key,
482
- loader: config.loader,
483
- namespace: config.default_namespace,
484
- separator: config.namespace_separator,
485
- **options,
486
- )
516
+ def component(identifier, **options)
517
+ if (component = booter.components.detect { |c| c.identifier == identifier })
518
+ component
519
+ else
520
+ Component.new(
521
+ identifier,
522
+ loader: config.loader,
523
+ namespace: config.default_namespace,
524
+ separator: config.namespace_separator,
525
+ **options,
526
+ )
527
+ end
487
528
  end
488
529
 
489
530
  # @api private
@@ -504,12 +545,18 @@ module Dry
504
545
  return self if key?(key)
505
546
 
506
547
  component(key).tap do |component|
507
- root_key = component.root_key
508
-
509
- if importer.key?(root_key)
510
- load_external_component(component.namespaced(root_key))
548
+ if component.boot?
549
+ booter.start(component)
511
550
  else
512
- load_local_component(component)
551
+ root_key = component.root_key
552
+
553
+ if (bootable_dep = component(root_key)).boot?
554
+ booter.start(bootable_dep)
555
+ elsif importer.key?(root_key)
556
+ load_imported_component(component.namespaced(root_key))
557
+ else
558
+ load_local_component(component)
559
+ end
513
560
  end
514
561
  end
515
562
 
@@ -536,7 +583,7 @@ module Dry
536
583
  end
537
584
 
538
585
  # @api private
539
- def load_external_component(component)
586
+ def load_imported_component(component)
540
587
  container = importer[component.namespace]
541
588
  container.load_component(component.identifier)
542
589
  importer.(component.namespace, container)
@@ -14,8 +14,8 @@ module Dry
14
14
  #
15
15
  # @api public
16
16
  ComponentFileMismatchError = Class.new(StandardError) do
17
- def initialize(filename, registered_booted_keys)
18
- super("Mismatch between filename +#{filename}+ and registered components +#{registered_booted_keys}+")
17
+ 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}")
19
19
  end
20
20
  end
21
21
 
@@ -1,5 +1,7 @@
1
1
  require 'concurrent/map'
2
2
 
3
+ require 'dry/system/settings'
4
+
3
5
  module Dry
4
6
  module System
5
7
  # Lifecycle booting DSL
@@ -23,9 +25,11 @@ module Dry
23
25
 
24
26
  attr_reader :triggers
25
27
 
28
+ attr_reader :opts
29
+
26
30
  # @api private
27
- def self.new(container, &block)
28
- cache.fetch_or_store([container, block].hash) do
31
+ def self.new(container, opts = {}, &block)
32
+ cache.fetch_or_store([container, opts, block].hash) do
29
33
  super
30
34
  end
31
35
  end
@@ -36,11 +40,13 @@ module Dry
36
40
  end
37
41
 
38
42
  # @api private
39
- def initialize(container, &block)
43
+ def initialize(container, opts, &block)
40
44
  @container = container
45
+ @settings = nil
41
46
  @statuses = []
42
47
  @triggers = {}
43
- instance_exec(container, &block)
48
+ @opts = opts
49
+ instance_exec(target, &block)
44
50
  end
45
51
 
46
52
  # @api private
@@ -53,6 +59,21 @@ module Dry
53
59
  end
54
60
  end
55
61
 
62
+ # @api private
63
+ def settings(&block)
64
+ component.settings(&block)
65
+ end
66
+
67
+ # @api private
68
+ def configure(&block)
69
+ component.configure(&block)
70
+ end
71
+
72
+ # @api private
73
+ def config
74
+ component.config
75
+ end
76
+
56
77
  # @api private
57
78
  def init(&block)
58
79
  trigger!(:init, &block)
@@ -71,7 +92,7 @@ module Dry
71
92
  # @api private
72
93
  def use(*names)
73
94
  names.each do |name|
74
- container.start(name)
95
+ target.start(name)
75
96
  end
76
97
  end
77
98
 
@@ -80,12 +101,22 @@ module Dry
80
101
  container.register(*args, &block)
81
102
  end
82
103
 
104
+ # @api private
105
+ def component
106
+ opts[:component]
107
+ end
108
+
109
+ # @api private
110
+ def target
111
+ component.container
112
+ end
113
+
83
114
  private
84
115
 
85
116
  # @api private
86
117
  def trigger!(name, &block)
87
118
  if triggers.key?(name)
88
- triggers[name].()
119
+ triggers[name].(target)
89
120
  elsif block
90
121
  triggers[name] = block
91
122
  end
@@ -93,7 +124,9 @@ module Dry
93
124
 
94
125
  # @api private
95
126
  def method_missing(meth, *args, &block)
96
- if container.key?(meth)
127
+ if target.key?(meth)
128
+ target[meth]
129
+ elsif container.key?(meth)
97
130
  container[meth]
98
131
  elsif ::Kernel.respond_to?(meth)
99
132
  ::Kernel.public_send(meth, *args, &block)
@@ -0,0 +1,46 @@
1
+ require 'concurrent/map'
2
+ require 'dry/system/components/bootable'
3
+
4
+ module Dry
5
+ module System
6
+ class Provider
7
+ attr_reader :identifier
8
+
9
+ attr_reader :options
10
+
11
+ attr_reader :components
12
+
13
+ def initialize(identifier, options)
14
+ @identifier = identifier
15
+ @options = options
16
+ @components = Concurrent::Map.new
17
+ end
18
+
19
+ def boot_path
20
+ options.fetch(:boot_path)
21
+ end
22
+
23
+ def boot_files
24
+ Dir[boot_path.join('**/*.rb')]
25
+ end
26
+
27
+ def register_component(name, fn)
28
+ components[name] = Components::Bootable.new(name, &fn)
29
+ end
30
+
31
+ def boot_file(name)
32
+ boot_files.detect { |path| Pathname(path).basename('.rb').to_s == name.to_s }
33
+ end
34
+
35
+ def component(name, options = {})
36
+ components.fetch(name).with(options)
37
+ end
38
+
39
+ def load_components
40
+ boot_files.each { |f| require f }
41
+ freeze
42
+ self
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ module Dry
2
+ module System
3
+ class ProviderRegistry
4
+ include Enumerable
5
+
6
+ attr_reader :items
7
+
8
+ def initialize
9
+ @items = []
10
+ end
11
+
12
+ def each(&block)
13
+ items.each(&block)
14
+ end
15
+
16
+ def register(identifier, options)
17
+ items << Provider.new(identifier, options)
18
+ end
19
+
20
+ def [](identifier)
21
+ detect { |provider| provider.identifier == identifier }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,59 @@
1
+ require "dry/core/class_builder"
2
+ require "dry/types"
3
+ require "dry/struct"
4
+
5
+ require "dry/system/settings/file_loader"
6
+
7
+ module Dry
8
+ module System
9
+ module Settings
10
+ class DSL < BasicObject
11
+ attr_reader :identifier
12
+
13
+ attr_reader :schema
14
+
15
+ def initialize(identifier, &block)
16
+ @identifier = identifier
17
+ @schema = {}
18
+ instance_eval(&block)
19
+ end
20
+
21
+ def call
22
+ Core::ClassBuilder.new(name: 'Configuration', parent: Settings::Configuration).call do |klass|
23
+ schema.each do |key, type|
24
+ klass.setting(key, type)
25
+ end
26
+ end
27
+ end
28
+
29
+ def key(name, type)
30
+ schema[name] = type
31
+ end
32
+ end
33
+
34
+ class Configuration < Dry::Struct
35
+ constructor_type :strict_with_defaults
36
+
37
+ def self.setting(*args)
38
+ attribute(*args)
39
+ end
40
+
41
+ def self.load(root, env)
42
+ env_data = load_files(root, env)
43
+
44
+ attributes = schema.each_with_object({}) do |(key, type), h|
45
+ value = ENV.fetch(key.to_s.upcase) { env_data[key.to_s.upcase] }
46
+ h[key] = value
47
+ end
48
+
49
+ new(attributes)
50
+ end
51
+
52
+ def self.load_files(root, env)
53
+ FileLoader.new.(root, env)
54
+ end
55
+ private_class_method :load_files
56
+ end
57
+ end
58
+ end
59
+ end