dry-system 0.6.0 → 0.7.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 +38 -4
  3. data/CONTRIBUTING.md +29 -0
  4. data/examples/custom_configuration_auto_register/Gemfile +5 -0
  5. data/examples/custom_configuration_auto_register/lib/entities/user.rb +7 -0
  6. data/examples/custom_configuration_auto_register/lib/user_repo.rb +5 -0
  7. data/examples/custom_configuration_auto_register/run.rb +8 -0
  8. data/examples/custom_configuration_auto_register/system/boot/persistence.rb +13 -0
  9. data/examples/custom_configuration_auto_register/system/container.rb +16 -0
  10. data/examples/custom_configuration_auto_register/system/import.rb +3 -0
  11. data/lib/dry/system/auto_registrar.rb +25 -15
  12. data/lib/dry/system/auto_registrar/configuration.rb +39 -0
  13. data/lib/dry/system/booter.rb +10 -5
  14. data/lib/dry/system/component.rb +8 -12
  15. data/lib/dry/system/constants.rb +1 -0
  16. data/lib/dry/system/container.rb +36 -15
  17. data/lib/dry/system/errors.rb +8 -8
  18. data/lib/dry/system/magic_comments_parser.rb +31 -0
  19. data/lib/dry/system/manual_registrar.rb +57 -0
  20. data/lib/dry/system/version.rb +1 -1
  21. data/spec/fixtures/components/no_register.rb +4 -0
  22. data/spec/fixtures/magic_comments/comments.rb +17 -0
  23. data/spec/fixtures/manual_registration/container/foo.rb +6 -0
  24. data/spec/fixtures/manual_registration/lib/test/foo.rb +9 -0
  25. data/spec/fixtures/multiple_namespaced_components/multiple/level/baz.rb +7 -0
  26. data/spec/fixtures/multiple_namespaced_components/multiple/level/foz.rb +6 -0
  27. data/spec/fixtures/other/config/boot/hell.rb +3 -0
  28. data/spec/fixtures/test/system/boot/hell.rb +3 -0
  29. data/spec/integration/container/lazy_loading/manual_registration_spec.rb +18 -0
  30. data/spec/integration/import_spec.rb +2 -2
  31. data/spec/unit/auto_registrar/configuration_spec.rb +26 -0
  32. data/spec/unit/component_spec.rb +0 -5
  33. data/spec/unit/container/auto_register_spec.rb +34 -6
  34. data/spec/unit/container/import_spec.rb +5 -22
  35. data/spec/unit/container_spec.rb +8 -0
  36. data/spec/unit/magic_comments_parser_spec.rb +41 -0
  37. metadata +36 -5
  38. data/.rubocop.yml +0 -34
  39. data/.rubocop_todo.yml +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: '085640c18c734b66700738f731ea66d0001cdc83'
4
- data.tar.gz: a6c6835df5df05045cddbc41615a581c11c4ea1d
3
+ metadata.gz: '0793e53e85b2a2fa0ea7da93bb11734362f87cd9'
4
+ data.tar.gz: 9d0cc9e0989fe9f071468e4e6b310a3fc009b06f
5
5
  SHA512:
6
- metadata.gz: 415e4f12b22eeb6e7f7b9f6f856b21953f1126b634d82cc17e9c3da3eab1f6aff30c261fddde7401495d6bccd1092fa7185a772031abf886732d913b1182aeee
7
- data.tar.gz: 3082441096b4e4208f1db30d817a816a809026b3cd07886b417edfc77be235167c54cdd9e5de4336109b88daa2633cca1c122a3f8fcbac8cea6421a099a0c633
6
+ metadata.gz: 50818887822ec2a8130910a359d45690312cd10c28b1537d5dbf0a45f98eb2f1abc0d70bf6f709313a939927a22c2d8e29fc8f2aa5806f53206d0b6390c37b6d
7
+ data.tar.gz: 1a350ffcba4a4bd7fca37be0976a55711aad5feaa70d493280aa477d4ea0723d5092fa35031c71c2b1d1ab6adaff6054d2a24e12fbdf4517b6eadb77e2850fe7
data/CHANGELOG.md CHANGED
@@ -1,13 +1,47 @@
1
- # 0.6.0 2016-02-02
1
+ # 0.7.0 - 2017-06-15
2
+
3
+ ### Added
4
+
5
+ - Added `manual_registrar` container setting (along with default `ManualRegistrar` implementation), and `registrations_dir` setting. These provide support for a well-established place for keeping files with manual container registrations (timriley)
6
+ - AutoRegistrar parses initial lines of Ruby source files for "magic comments" when auto-registering components. An `# auto_register: false` magic comment will prevent a Ruby file from being auto-registered (timriley)
7
+ - `Container.auto_register!`, when called with a block, yields a configuration object to control the auto-registration behavior for that path, with support for configuring 2 different aspects of auto-registration behavior (both optional):
8
+
9
+ ```ruby
10
+ class MyContainer < Dry::System::Container
11
+ auto_register!('lib') do |config|
12
+ config.instance do |component|
13
+ # custom logic for initializing a component
14
+ end
15
+
16
+ config.exclude do |component|
17
+ # return true to skip auto-registration of the component, e.g.
18
+ # component.path =~ /entities/
19
+ end
20
+ end
21
+ end
22
+ ```
23
+
24
+ - A helpful error will be raised if a bootable component's finalize block name doesn't match its boot file name (GustavoCaso)
25
+
26
+ ### Changed
27
+
28
+ - The `default_namespace` container setting now supports multi-level namespaces (GustavoCaso)
29
+ - `Container.auto_register!` yields a configuration block instead of a block for returning a custom instance (see above) (GustavoCaso)
30
+ - `Container.import` now requires an explicit local name for the imported container (e.g. `import(local_name: AnotherContainer)`) (timriley)
31
+
32
+ [Compare v0.6.0...v0.7.0](https://github.com/dry-rb/dry-system/compare/v0.6.0...v0.7.0)
33
+
34
+
35
+ # 0.6.0 - 2016-02-02
2
36
 
3
37
  ### Changed
4
38
 
5
39
  * Lazy load components as they are resolved, rather than on injection (timriley)
6
40
  * Perform registration even though component already required (blelump)
7
41
 
8
- [Compare v0.5.0...v0.6.0](https://github.com/dry-rb/dry-system/compare/v0.5.1...v0.6.0)
42
+ [Compare v0.5.1...v0.6.0](https://github.com/dry-rb/dry-system/compare/v0.5.1...v0.6.0)
9
43
 
10
- # 0.5.1 2016-08-23
44
+ # 0.5.1 - 2016-08-23
11
45
 
12
46
  ### Fixed
13
47
 
@@ -15,7 +49,7 @@
15
49
 
16
50
  [Compare v0.5.0...v0.5.1](https://github.com/dry-rb/dry-system/compare/v0.5.0...v0.5.1)
17
51
 
18
- # 0.5.0 2016-08-15
52
+ # 0.5.0 - 2016-08-15
19
53
 
20
54
  This is a major refactoring with better internal APIs and improved support
21
55
  for multi-container setups. As part of this release `dry-system` has been renamed to `dry-system`.
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,29 @@
1
+ # Issue Guidelines
2
+
3
+ ## Reporting bugs
4
+
5
+ If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated.
6
+
7
+ ## Reporting feature requests
8
+
9
+ Report a feature request **only after discussing it first on [discuss.dry-rb.org](https://discuss.dry-rb.org)** where it was accepted. Please provide a concise description of the feature, don't link to a discussion thread, and instead summarize what was discussed.
10
+
11
+ ## Reporting questions, support requests, ideas, concerns etc.
12
+
13
+ **PLEASE DON'T** - use [discuss.dry-rb.org](http://discuss.dry-rb.org) instead.
14
+
15
+ # Pull Request Guidelines
16
+
17
+ A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc.
18
+
19
+ Other requirements:
20
+
21
+ 1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue.
22
+ 2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style.
23
+ 3) Add API documentation if it's a new feature
24
+ 4) Update API documentation if it changes an existing feature
25
+ 5) Bonus points for sending a PR to [github.com/dry-rb/dry-rb.org](github.com/dry-rb/dry-rb.org) which updates user documentation and guides
26
+
27
+ # Asking for help
28
+
29
+ If these guidelines aren't helpful, and you're stuck, please post a message on [discuss.dry-rb.org](https://discuss.dry-rb.org).
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'dry-system', path: '../..'
4
+ gem 'sequel'
5
+ gem 'sqlite3'
@@ -0,0 +1,7 @@
1
+ require 'import'
2
+
3
+ module Entities
4
+ class User
5
+ include Import['persistence.db']
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ require 'import'
2
+
3
+ class UserRepo
4
+ include Import['persistence.db']
5
+ end
@@ -0,0 +1,8 @@
1
+ require 'bundler/setup'
2
+ require_relative 'system/container'
3
+
4
+ App.finalize!
5
+
6
+ user_repo = App['user_repo']
7
+ puts "User has not been loaded" unless App.key?('entities.user')
8
+ puts user_repo.db.inspect
@@ -0,0 +1,13 @@
1
+ App.finalize(:persistence) do |persistence|
2
+ init do
3
+ require 'sequel'
4
+ end
5
+
6
+ start do
7
+ persistence.register('persistence.db', Sequel.connect('sqlite::memory'))
8
+ end
9
+
10
+ stop do
11
+ db.close_connection
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ require 'dry/system/container'
2
+
3
+ class App < Dry::System::Container
4
+ load_paths!('lib', 'system')
5
+
6
+ auto_register!('lib') do |config|
7
+ config.instance do |component|
8
+ # some custom initialization logic
9
+ component.instance
10
+ end
11
+
12
+ config.exclude do |component|
13
+ component.path =~ /entities/
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'container'
2
+
3
+ Import = App.injector
@@ -1,4 +1,6 @@
1
1
  require 'dry/system/constants'
2
+ require 'dry/system/magic_comments_parser'
3
+ require 'dry/system/auto_registrar/configuration'
2
4
 
3
5
  module Dry
4
6
  module System
@@ -6,7 +8,7 @@ module Dry
6
8
  #
7
9
  # This is currently configured by default for every System::Container.
8
10
  # Auto-registrar objects are responsible for loading files from configured
9
- # auto-register paths and registering components automatically withing the
11
+ # auto-register paths and registering components automatically within the
10
12
  # container.
11
13
  #
12
14
  # @api private
@@ -26,14 +28,14 @@ module Dry
26
28
  end
27
29
 
28
30
  # @api private
29
- def call(dir, &block)
31
+ def call(dir)
32
+ registration_config = Configuration.new
33
+ yield(registration_config) if block_given?
30
34
  components(dir).each do |component|
35
+ next if !component.auto_register? || registration_config.exclude.(component)
36
+
31
37
  container.require_component(component) do
32
- if block
33
- register(component.identifier, yield(component))
34
- else
35
- register(component.identifier) { component.instance }
36
- end
38
+ register(component.identifier) { registration_config.instance.(component) }
37
39
  end
38
40
  end
39
41
  end
@@ -42,23 +44,31 @@ module Dry
42
44
 
43
45
  # @api private
44
46
  def components(dir)
45
- paths(dir).
46
- map { |path| component(path) }.
47
+ files(dir).
48
+ map { |file_name| [file_name, file_options(file_name)] }.
49
+ map { |(file_name, options)| component(relative_path(dir, file_name), **options) }.
47
50
  reject { |component| key?(component.identifier) }
48
51
  end
49
52
 
50
53
  # @api private
51
- def paths(dir)
54
+ def files(dir)
55
+ Dir["#{root}/#{dir}/**/*.rb"]
56
+ end
57
+
58
+ # @api private
59
+ def relative_path(dir, file_path)
52
60
  dir_root = root.join(dir.to_s.split('/')[0])
61
+ file_path.to_s.sub("#{dir_root}/", '').sub(RB_EXT, EMPTY_STRING)
62
+ end
53
63
 
54
- Dir["#{root}/#{dir}/**/*.rb"].map { |path|
55
- path.to_s.sub("#{dir_root}/", '').sub(RB_EXT, EMPTY_STRING)
56
- }
64
+ # @api private
65
+ def file_options(file_name)
66
+ MagicCommentsParser.(file_name)
57
67
  end
58
68
 
59
69
  # @api private
60
- def component(path)
61
- container.component(path)
70
+ def component(path, options)
71
+ container.component(path, options)
62
72
  end
63
73
 
64
74
  # @api private
@@ -0,0 +1,39 @@
1
+ module Dry
2
+ module System
3
+ class AutoRegistrar
4
+ # Default auto_registrar configuration
5
+ #
6
+ # This is currently configured by default for every System::Container.
7
+ # Configuration allows to define custom initializtion logic as well
8
+ # as exclusion, for each component that is been register by Dry::System
9
+ # auto-registration.
10
+ #
11
+ # @api private
12
+ class Configuration
13
+ DEFAULT_INSTANCE = -> component { component.instance }.freeze
14
+ FALSE_PROC = -> * { false }.freeze
15
+
16
+ def self.setting(name)
17
+ define_method(name) do |&block|
18
+ ivar = "@#{name}"
19
+
20
+ if block
21
+ instance_variable_set(ivar, block)
22
+ else
23
+ instance_variable_get(ivar)
24
+ end
25
+ end
26
+ end
27
+
28
+ setting :exclude
29
+ setting :instance
30
+
31
+ # @api private
32
+ def initialize
33
+ @instance = DEFAULT_INSTANCE
34
+ @exclude = FALSE_PROC
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -66,11 +66,11 @@ module Dry
66
66
  def call(name)
67
67
  container, finalizer = finalizers[name]
68
68
 
69
- if finalizer
70
- lifecycle = Lifecycle.new(container, &finalizer)
71
- yield(lifecycle) if block_given?
72
- lifecycle
73
- end
69
+ raise ComponentFileMismatchError.new(name, registered_booted_keys) unless finalizer
70
+
71
+ lifecycle = Lifecycle.new(container, &finalizer)
72
+ yield(lifecycle) if block_given?
73
+ lifecycle
74
74
  end
75
75
 
76
76
  # @api private
@@ -81,6 +81,11 @@ module Dry
81
81
 
82
82
  private
83
83
 
84
+ # @api private
85
+ def registered_booted_keys
86
+ finalizers.keys - booted.keys
87
+ end
88
+
84
89
  # @api private
85
90
  def boot_files
86
91
  path.join('**/*.rb').to_s
@@ -47,9 +47,7 @@ module Dry
47
47
  options = DEFAULT_OPTIONS.merge(options || {})
48
48
 
49
49
  ns, sep = options.values_at(:namespace, :separator)
50
-
51
- ns_name = ensure_valid_namespace(ns, sep)
52
- identifier = ensure_valid_identifier(name, ns_name, sep)
50
+ identifier = ensure_valid_identifier(name, ns, sep)
53
51
 
54
52
  path = name.to_s.gsub(sep, PATH_SEPARATOR)
55
53
  loader = options.fetch(:loader, Loader).new(path)
@@ -59,21 +57,14 @@ module Dry
59
57
  end
60
58
 
61
59
  # @api private
62
- def self.ensure_valid_namespace(ns, sep)
63
- ns_name = ns.to_s
64
- raise InvalidNamespaceError, ns_name if ns && ns_name.include?(sep)
65
- ns_name
66
- end
67
-
68
- # @api private
69
- def self.ensure_valid_identifier(name, ns_name, sep)
60
+ def self.ensure_valid_identifier(name, ns, sep)
70
61
  keys = name.to_s.scan(WORD_REGEX)
71
62
 
72
63
  if keys.uniq.size != keys.size
73
64
  raise InvalidComponentError, name, 'duplicated keys in the name'
74
65
  end
75
66
 
76
- keys.reject { |s| ns_name == s }.join(sep)
67
+ keys.reject { |s| ns.to_s.include?(s) }.join(sep)
77
68
  end
78
69
 
79
70
  # @api private
@@ -152,6 +143,11 @@ module Dry
152
143
  options[:namespace]
153
144
  end
154
145
 
146
+ # @api private
147
+ def auto_register?
148
+ !!options.fetch(:auto_register) { true }
149
+ end
150
+
155
151
  # @api private
156
152
  def root_key
157
153
  namespaces.first
@@ -1,6 +1,7 @@
1
1
  module Dry
2
2
  module System
3
3
  RB_EXT = '.rb'.freeze
4
+ RB_GLOB = '*.rb'.freeze
4
5
  EMPTY_STRING = ''.freeze
5
6
  PATH_SEPARATOR = '/'.freeze
6
7
  DEFAULT_SEPARATOR = '.'.freeze
@@ -8,6 +8,7 @@ require 'dry/system/errors'
8
8
  require 'dry/system/loader'
9
9
  require 'dry/system/booter'
10
10
  require 'dry/system/auto_registrar'
11
+ require 'dry/system/manual_registrar'
11
12
  require 'dry/system/importer'
12
13
  require 'dry/system/component'
13
14
  require 'dry/system/constants'
@@ -67,10 +68,12 @@ module Dry
67
68
  setting :default_namespace
68
69
  setting :root, Pathname.pwd.freeze
69
70
  setting :system_dir, 'system'.freeze
71
+ setting :registrations_dir, 'container'.freeze
70
72
  setting :auto_register, []
71
73
  setting :loader, Dry::System::Loader
72
74
  setting :booter, Dry::System::Booter
73
75
  setting :auto_registrar, Dry::System::AutoRegistrar
76
+ setting :manual_registrar, Dry::System::ManualRegistrar
74
77
  setting :importer, Dry::System::Importer
75
78
 
76
79
  class << self
@@ -101,7 +104,6 @@ module Dry
101
104
  # class Core < Dry::System::Container
102
105
  # configure do |config|
103
106
  # config.root = Pathname("/path/to/app")
104
- # config.name = :core
105
107
  # config.auto_register = %w(lib/apis lib/core)
106
108
  # end
107
109
  # end
@@ -112,14 +114,13 @@ module Dry
112
114
  # class MyApp < Dry::System::Container
113
115
  # configure do |config|
114
116
  # config.root = Pathname("/path/to/app")
115
- # config.name = :core
116
117
  # config.auto_register = %w(lib/apis lib/core)
117
118
  # end
118
119
  #
119
120
  # import core: Core
120
121
  # end
121
122
  #
122
- # @param other [Hash,Dry::Container::Namespace,Dry::System::Container]
123
+ # @param other [Hash,Dry::Container::Namespace]
123
124
  #
124
125
  # @api public
125
126
  def import(other)
@@ -127,9 +128,7 @@ module Dry
127
128
  when Hash then importer.register(other)
128
129
  when Dry::Container::Namespace then super
129
130
  else
130
- if other < System::Container
131
- importer.register(other.config.name => other)
132
- end
131
+ raise ArgumentError, "+other+ must be a hash of names and systems, or a Dry::Container namespace"
133
132
  end
134
133
  end
135
134
 
@@ -247,6 +246,7 @@ module Dry
247
246
 
248
247
  importer.finalize!
249
248
  booter.finalize!
249
+ manual_registrar.finalize!
250
250
  auto_registrar.finalize!
251
251
 
252
252
  freeze
@@ -312,11 +312,18 @@ module Dry
312
312
  self
313
313
  end
314
314
 
315
+ # @api public
316
+ def load_registrations!(name)
317
+ manual_registrar.(name)
318
+ self
319
+ end
320
+
315
321
  # Auto-registers components from the provided directory
316
322
  #
317
323
  # Typically you want to configure auto_register directories, and it will
318
324
  # work automatically. Use this method in cases where you want to have an
319
- # explicit way where some components are auto-registered.
325
+ # explicit way where some components are auto-registered, or if you want
326
+ # to exclude some components from been auto-registered
320
327
  #
321
328
  # @example
322
329
  # class MyApp < Dry::System::Container
@@ -328,15 +335,21 @@ module Dry
328
335
  # auto_register!('lib/core')
329
336
  #
330
337
  # # with a dir and a custom registration block
331
- # auto_register!('lib/core') do |component|
332
- # # custom way of initializing a component
338
+ # auto_register!('lib/core') do |config|
339
+ # config.instance do |component|
340
+ # # custom way of initializing a component
341
+ # end
342
+ #
343
+ # config.exclude do |component|
344
+ # # return true to exclude component from auto-registration
345
+ # end
333
346
  # end
334
347
  # end
335
348
  #
336
349
  # @param [String] dir The dir name relative to the root dir
337
350
  #
338
- # @yield [Component]
339
- # @see [Component]
351
+ # @yield AutoRegistrar::Configuration
352
+ # @see AutoRegistrar::Configuration
340
353
  #
341
354
  # @return [self]
342
355
  #
@@ -435,18 +448,24 @@ module Dry
435
448
  @auto_registrar ||= config.auto_registrar.new(self)
436
449
  end
437
450
 
451
+ # @api private
452
+ def manual_registrar
453
+ @manual_registrar ||= config.manual_registrar.new(self)
454
+ end
455
+
438
456
  # @api private
439
457
  def importer
440
458
  @importer ||= config.importer.new(self)
441
459
  end
442
460
 
443
461
  # @api private
444
- def component(key)
462
+ def component(key, **options)
445
463
  Component.new(
446
464
  key,
447
465
  loader: config.loader,
448
466
  namespace: config.default_namespace,
449
- separator: config.namespace_separator
467
+ separator: config.namespace_separator,
468
+ **options,
450
469
  )
451
470
  end
452
471
 
@@ -483,15 +502,17 @@ module Dry
483
502
  private
484
503
 
485
504
  # @api private
486
- def load_local_component(component, fallback = false)
505
+ def load_local_component(component, default_namespace_fallback = false)
487
506
  if component.bootable?(booter.path) || component.file_exists?(load_paths)
488
507
  booter.boot_dependency(component) unless frozen?
489
508
 
490
509
  require_component(component) do
491
510
  register(component.identifier) { component.instance }
492
511
  end
493
- elsif !fallback
512
+ elsif !default_namespace_fallback
494
513
  load_local_component(component.prepend(config.default_namespace), true)
514
+ elsif manual_registrar.file_exists?(component)
515
+ manual_registrar.(component)
495
516
  else
496
517
  raise ComponentLoadError, component
497
518
  end
@@ -10,21 +10,21 @@ module Dry
10
10
  end
11
11
  end
12
12
 
13
- # Error raised when a resolved component couldn't be found
13
+ # Error raised when booter file do not match with register component
14
14
  #
15
15
  # @api public
16
- ComponentLoadError = Class.new(StandardError) do
17
- def initialize(component)
18
- super("could not load component #{component.inspect}")
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}+")
19
19
  end
20
20
  end
21
21
 
22
- # Error raised when invalid namespace name was provided
22
+ # Error raised when a resolved component couldn't be found
23
23
  #
24
24
  # @api public
25
- InvalidNamespaceError = Class.new(StandardError) do
26
- def initialize(ns)
27
- super("Namespace #{ns} cannot include a separator")
25
+ ComponentLoadError = Class.new(StandardError) do
26
+ def initialize(component)
27
+ super("could not load component #{component.inspect}")
28
28
  end
29
29
  end
30
30
 
@@ -0,0 +1,31 @@
1
+ module Dry
2
+ module System
3
+ class MagicCommentsParser
4
+ VALID_LINE_RE = /^(#.*)?$/.freeze
5
+ COMMENT_RE = /^#\s+(?<name>[A-Za-z]{1}[A-Za-z0-9_]+):\s+(?<value>.+?)$/.freeze
6
+
7
+ COERCIONS = {
8
+ 'true' => true,
9
+ 'false' => false,
10
+ }.freeze
11
+
12
+ def self.call(file_name)
13
+ {}.tap do |options|
14
+ File.foreach(file_name) do |line|
15
+ break unless line =~ VALID_LINE_RE
16
+
17
+ if (comment = line.match(COMMENT_RE))
18
+ options[comment[:name].to_sym] = coerce(comment[:value])
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def self.coerce(value)
27
+ COERCIONS.fetch(value) { value }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,57 @@
1
+ require 'pathname'
2
+ require 'dry/system/constants'
3
+
4
+ module Dry
5
+ module System
6
+ # Default manual registration implementation
7
+ #
8
+ # This is currently configured by default for every System::Container.
9
+ # Manual registrar objects are responsible for loading files from configured
10
+ # manual registration paths, which should hold code to explicitly register
11
+ # certain objects with the container.
12
+ #
13
+ # @api private
14
+ class ManualRegistrar
15
+ attr_reader :container
16
+
17
+ attr_reader :config
18
+
19
+ def initialize(container)
20
+ @container = container
21
+ @config = container.config
22
+ end
23
+
24
+ # @api private
25
+ def finalize!
26
+ Dir[registrations_dir.join(RB_GLOB)].each do |file|
27
+ call(File.basename(file, RB_EXT))
28
+ end
29
+ end
30
+
31
+ # @api private
32
+ def call(name)
33
+ name = name.respond_to?(:root_key) ? name.root_key.to_s : name
34
+
35
+ require(root.join(config.registrations_dir, name))
36
+ end
37
+
38
+ def file_exists?(name)
39
+ name = name.respond_to?(:root_key) ? name.root_key.to_s : name
40
+
41
+ File.exist?(File.join(registrations_dir, "#{name}#{RB_EXT}"))
42
+ end
43
+
44
+ private
45
+
46
+ # @api private
47
+ def registrations_dir
48
+ root.join(config.registrations_dir)
49
+ end
50
+
51
+ # @api private
52
+ def root
53
+ Pathname(container.root)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module System
3
- VERSION = '0.6.0'.freeze
3
+ VERSION = '0.7.0'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,4 @@
1
+ # auto_register: false
2
+
3
+ class NoRegister
4
+ end
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This is a file with a mix of valid and invalid magic comments
4
+
5
+ # valid_comment: hello
6
+ # true_comment: true
7
+ # false_comment: false
8
+ # comment_123: alpha-numeric and underscores allowed
9
+ # 123_will_not_match: will not match
10
+ # not-using-underscores: value for comment using dashes
11
+
12
+ # not_at_start_of_line: will not match
13
+
14
+ module Test
15
+ end
16
+
17
+ # after_code: will not match
@@ -0,0 +1,6 @@
1
+ Test::Container.namespace(:foo) do |container|
2
+ container.register('special') do
3
+ require 'test/foo'
4
+ Test::Foo.new('special')
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module Test
2
+ class Foo
3
+ attr_reader :name
4
+
5
+ def initialize(name)
6
+ @name = name
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Multiple
2
+ module Level
3
+ class Baz
4
+ include Test::Container.injector["foz"]
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module Multiple
2
+ module Level
3
+ class Foz
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ Test::Container.finalize :heaven do |container|
2
+ container.register('heaven', 'string')
3
+ end
@@ -0,0 +1,3 @@
1
+ Test::Container.finalize :heaven do |container|
2
+ container.register('heaven', 'string')
3
+ end
@@ -0,0 +1,18 @@
1
+ RSpec.describe 'Lazy-loading manual registration files' do
2
+ before do
3
+ module Test
4
+ class Container < Dry::System::Container
5
+ configure do |config|
6
+ config.root = SPEC_ROOT.join('fixtures/manual_registration').realpath
7
+ end
8
+
9
+ load_paths!('lib')
10
+ end
11
+ end
12
+ end
13
+
14
+ it 'loads a manual registration file if the component could not be found' do
15
+ expect(Test::Container['foo.special']).to be_a(Test::Foo)
16
+ expect(Test::Container['foo.special'].name).to eq "special"
17
+ end
18
+ end
@@ -36,7 +36,7 @@ RSpec.describe 'Lazy-booting external deps' do
36
36
 
37
37
  before do
38
38
  module Test
39
- Umbrella.import(App)
39
+ Umbrella.import(main: App)
40
40
  Import = Umbrella.injector
41
41
  end
42
42
  end
@@ -53,7 +53,7 @@ RSpec.describe 'Lazy-booting external deps' do
53
53
 
54
54
  before do
55
55
  module Test
56
- App.import(Umbrella)
56
+ App.import(core: Umbrella)
57
57
  Import = App.injector
58
58
  end
59
59
  end
@@ -0,0 +1,26 @@
1
+ require 'dry/system/auto_registrar/configuration'
2
+
3
+ RSpec.describe Dry::System::AutoRegistrar::Configuration do
4
+ subject(:auto_registration_conf) { Dry::System::AutoRegistrar::Configuration.new }
5
+
6
+ describe "deafult values" do
7
+ it "will setup exclude default proc" do
8
+ expect(subject.exclude.(8)).to eq false
9
+ end
10
+
11
+ it "will setup instance default proc" do
12
+ component = double("component")
13
+ expect(component).to receive(:instance)
14
+ subject.instance.(component)
15
+ end
16
+ end
17
+
18
+ describe "add custom proc object to configuration" do
19
+ it "execute proc that was previously save" do
20
+ proc = Proc.new { |value| value + 1 }
21
+ subject.instance(&proc)
22
+ result = subject.instance.(5)
23
+ expect(result).to eq 6
24
+ end
25
+ end
26
+ end
@@ -12,11 +12,6 @@ RSpec.describe Dry::System::Component do
12
12
  expect(create.()).to be(create.())
13
13
  end
14
14
 
15
- it 'raises when namespace includes a separator' do
16
- expect { Dry::System::Component.new('foo.bar.baz', namespace: 'foo.bar') }
17
- .to raise_error(Dry::System::InvalidNamespaceError, /foo\.bar/)
18
- end
19
-
20
15
  it 'raises when identifier/path has duplicated keys' do
21
16
  expect { Dry::System::Component.new('foo.bar.foo', namespace: 'foo') }
22
17
  .to raise_error(Dry::System::InvalidComponentError, /foo\.bar\.foo/)
@@ -16,9 +16,13 @@ RSpec.describe Dry::System::Container, '.auto_register!' do
16
16
  it { expect(Test::Container['foo']).to be_an_instance_of(Foo) }
17
17
  it { expect(Test::Container['bar']).to be_an_instance_of(Bar) }
18
18
  it { expect(Test::Container['bar.baz']).to be_an_instance_of(Bar::Baz) }
19
+
20
+ it "doesn't register files with inline option 'auto_register: false'" do
21
+ expect(Test::Container.key?('no_register')).to eql false
22
+ end
19
23
  end
20
24
 
21
- context 'with custom registration block' do
25
+ context 'with custom configuration block' do
22
26
  before do
23
27
  class Test::Container < Dry::System::Container
24
28
  configure do |config|
@@ -29,14 +33,21 @@ RSpec.describe Dry::System::Container, '.auto_register!' do
29
33
  end
30
34
  end
31
35
 
32
- it 'yields found components' do
33
- Test::Container.auto_register!('components') do |component|
34
- component.identifier
36
+ it 'exclude specific components' do
37
+ Test::Container.auto_register!('components') do |config|
38
+ config.instance do |component|
39
+ component.identifier
40
+ end
41
+
42
+ config.exclude do |component|
43
+ component.path =~ /bar/
44
+ end
35
45
  end
36
46
 
37
47
  expect(Test::Container['foo']).to eql('foo')
38
- expect(Test::Container['bar']).to eql('bar')
39
- expect(Test::Container['bar.baz']).to eql('bar.baz')
48
+
49
+ expect(Test::Container.key?('bar')).to eql false
50
+ expect(Test::Container.key?('bar.baz')).to eql false
40
51
  end
41
52
  end
42
53
 
@@ -58,6 +69,23 @@ RSpec.describe Dry::System::Container, '.auto_register!' do
58
69
  specify { expect(Test::Container['foo']).to be_a(Namespaced::Foo) }
59
70
  end
60
71
 
72
+ context 'standard loader with a default namespace with multiple level' do
73
+ before do
74
+ class Test::Container < Dry::System::Container
75
+ configure do |config|
76
+ config.root = SPEC_ROOT.join('fixtures').realpath
77
+ config.default_namespace = 'multiple.level'
78
+ end
79
+
80
+ load_paths!('multiple_namespaced_components')
81
+ auto_register!('multiple_namespaced_components')
82
+ end
83
+ end
84
+
85
+ specify { expect(Test::Container['baz']).to be_a(Multiple::Level::Baz) }
86
+ specify { expect(Test::Container['foz']).to be_a(Multiple::Level::Foz) }
87
+ end
88
+
61
89
  context 'with a custom loader' do
62
90
  before do
63
91
  class Test::Loader < Dry::System::Loader
@@ -9,31 +9,14 @@ RSpec.describe Dry::System::Container, '.import' do
9
9
  end
10
10
  end
11
11
 
12
- shared_examples_for 'an extended container' do
13
- it 'imports one container into another' do
14
- expect(app.key?('persistence.users')).to be(false)
12
+ it 'imports one container into another' do
13
+ app.import(persistence: db)
15
14
 
16
- app.finalize!
15
+ expect(app.key?('persistence.users')).to be(false)
17
16
 
18
- expect(app['persistence.users']).to eql(%w(jane joe))
19
- end
20
- end
21
-
22
- context 'when a container has a name' do
23
- before do
24
- db.configure { |c| c.name = :persistence }
25
- app.import(db)
26
- end
27
-
28
- it_behaves_like 'an extended container'
29
- end
30
-
31
- context 'when container does not have a name' do
32
- before do
33
- app.import(persistence: db)
34
- end
17
+ app.finalize!
35
18
 
36
- it_behaves_like 'an extended container'
19
+ expect(app['persistence.users']).to eql(%w(jane joe))
37
20
  end
38
21
 
39
22
  describe 'import module' do
@@ -142,6 +142,14 @@ RSpec.describe Dry::System::Container do
142
142
  'component identifier +foo+ is invalid or boot file is missing'
143
143
  )
144
144
  end
145
+
146
+ describe "missmatch betwenn finalize name and registered component" do
147
+ it "raises a meaningful error" do
148
+ expect{
149
+ container.boot!(:hell)
150
+ }.to raise_error(Dry::System::ComponentFileMismatchError)
151
+ end
152
+ end
145
153
  end
146
154
 
147
155
  context 'with the default core dir' do
@@ -0,0 +1,41 @@
1
+ require 'dry/system/magic_comments_parser'
2
+
3
+ RSpec.describe Dry::System::MagicCommentsParser, '.call' do
4
+ let(:file_name) { SPEC_ROOT.join('fixtures/magic_comments/comments.rb') }
5
+ let(:comments) { described_class.(file_name) }
6
+
7
+ it 'makes comment names available as symbols' do
8
+ expect(comments.key?(:valid_comment)).to eql true
9
+ end
10
+
11
+ it 'finds magic comments after other commented lines or blank lines' do
12
+ expect(comments[:valid_comment]).to eq 'hello'
13
+ end
14
+
15
+ it 'does not match comments with invalid names' do
16
+ expect(comments.values).not_to include 'value for comment using dashes'
17
+ end
18
+
19
+ it 'supports comment names with alpha-numeric characters and underscores (numbers not allowed for first character)' do
20
+ expect(comments[:comment_123]).to eq 'alpha-numeric and underscores allowed'
21
+ expect(comments.keys).not_to include(:"123_will_not_match")
22
+ end
23
+
24
+ it 'only matches comments at the start of the line' do
25
+ expect(comments.key?(:not_at_start_of_line)).to eql false
26
+ end
27
+
28
+ it 'does not match any comments after any lines of code' do
29
+ expect(comments.key?(:after_code)).to eql false
30
+ end
31
+
32
+ describe 'coercions' do
33
+ it 'coerces "true" to true' do
34
+ expect(comments[:true_comment]).to eq true
35
+ end
36
+
37
+ it 'coerces "false" to false' do
38
+ expect(comments[:false_comment]).to eq false
39
+ end
40
+ end
41
+ 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.6.0
4
+ version: 0.7.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: 2017-02-02 00:00:00.000000000 Z
11
+ date: 2017-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inflecto
@@ -145,16 +145,22 @@ extra_rdoc_files: []
145
145
  files:
146
146
  - ".gitignore"
147
147
  - ".rspec"
148
- - ".rubocop.yml"
149
- - ".rubocop_todo.yml"
150
148
  - ".travis.yml"
151
149
  - ".yardopts"
152
150
  - CHANGELOG.md
151
+ - CONTRIBUTING.md
153
152
  - Gemfile
154
153
  - LICENSE
155
154
  - README.md
156
155
  - Rakefile
157
156
  - dry-system.gemspec
157
+ - examples/custom_configuration_auto_register/Gemfile
158
+ - examples/custom_configuration_auto_register/lib/entities/user.rb
159
+ - examples/custom_configuration_auto_register/lib/user_repo.rb
160
+ - examples/custom_configuration_auto_register/run.rb
161
+ - examples/custom_configuration_auto_register/system/boot/persistence.rb
162
+ - examples/custom_configuration_auto_register/system/container.rb
163
+ - examples/custom_configuration_auto_register/system/import.rb
158
164
  - examples/standalone/Gemfile
159
165
  - examples/standalone/lib/user_repo.rb
160
166
  - examples/standalone/run.rb
@@ -164,6 +170,7 @@ files:
164
170
  - lib/dry-system.rb
165
171
  - lib/dry/system.rb
166
172
  - lib/dry/system/auto_registrar.rb
173
+ - lib/dry/system/auto_registrar/configuration.rb
167
174
  - lib/dry/system/booter.rb
168
175
  - lib/dry/system/component.rb
169
176
  - lib/dry/system/constants.rb
@@ -172,10 +179,13 @@ files:
172
179
  - lib/dry/system/importer.rb
173
180
  - lib/dry/system/lifecycle.rb
174
181
  - lib/dry/system/loader.rb
182
+ - lib/dry/system/magic_comments_parser.rb
183
+ - lib/dry/system/manual_registrar.rb
175
184
  - lib/dry/system/version.rb
176
185
  - spec/fixtures/components/bar.rb
177
186
  - spec/fixtures/components/bar/baz.rb
178
187
  - spec/fixtures/components/foo.rb
188
+ - spec/fixtures/components/no_register.rb
179
189
  - spec/fixtures/import_test/config/application.yml
180
190
  - spec/fixtures/import_test/lib/test/bar.rb
181
191
  - spec/fixtures/import_test/lib/test/foo.rb
@@ -187,9 +197,15 @@ files:
187
197
  - spec/fixtures/lazytest/lib/test/models/book.rb
188
198
  - spec/fixtures/lazytest/lib/test/models/user.rb
189
199
  - spec/fixtures/lazytest/system/boot/bar.rb
200
+ - spec/fixtures/magic_comments/comments.rb
201
+ - spec/fixtures/manual_registration/container/foo.rb
202
+ - spec/fixtures/manual_registration/lib/test/foo.rb
203
+ - spec/fixtures/multiple_namespaced_components/multiple/level/baz.rb
204
+ - spec/fixtures/multiple_namespaced_components/multiple/level/foz.rb
190
205
  - spec/fixtures/namespaced_components/namespaced/bar.rb
191
206
  - spec/fixtures/namespaced_components/namespaced/foo.rb
192
207
  - spec/fixtures/other/config/boot/bar.rb
208
+ - spec/fixtures/other/config/boot/hell.rb
193
209
  - spec/fixtures/other/lib/test/dep.rb
194
210
  - spec/fixtures/other/lib/test/foo.rb
195
211
  - spec/fixtures/other/lib/test/models.rb
@@ -207,11 +223,14 @@ files:
207
223
  - spec/fixtures/test/system/boot/bar.rb
208
224
  - spec/fixtures/test/system/boot/client.rb
209
225
  - spec/fixtures/test/system/boot/db.rb
226
+ - spec/fixtures/test/system/boot/hell.rb
210
227
  - spec/fixtures/test/system/boot/logger.rb
211
228
  - spec/fixtures/umbrella/system/boot/db.rb
212
229
  - spec/integration/boot_spec.rb
230
+ - spec/integration/container/lazy_loading/manual_registration_spec.rb
213
231
  - spec/integration/import_spec.rb
214
232
  - spec/spec_helper.rb
233
+ - spec/unit/auto_registrar/configuration_spec.rb
215
234
  - spec/unit/component_spec.rb
216
235
  - spec/unit/container/auto_register_spec.rb
217
236
  - spec/unit/container/finalize_spec.rb
@@ -219,6 +238,7 @@ files:
219
238
  - spec/unit/container/injector_spec.rb
220
239
  - spec/unit/container_spec.rb
221
240
  - spec/unit/loader_spec.rb
241
+ - spec/unit/magic_comments_parser_spec.rb
222
242
  homepage: http://dry-rb.org/gems/dry-system
223
243
  licenses:
224
244
  - MIT
@@ -239,7 +259,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
239
259
  version: '0'
240
260
  requirements: []
241
261
  rubyforge_project:
242
- rubygems_version: 2.6.9
262
+ rubygems_version: 2.6.11
243
263
  signing_key:
244
264
  specification_version: 4
245
265
  summary: Organize your code into reusable components
@@ -247,6 +267,7 @@ test_files:
247
267
  - spec/fixtures/components/bar.rb
248
268
  - spec/fixtures/components/bar/baz.rb
249
269
  - spec/fixtures/components/foo.rb
270
+ - spec/fixtures/components/no_register.rb
250
271
  - spec/fixtures/import_test/config/application.yml
251
272
  - spec/fixtures/import_test/lib/test/bar.rb
252
273
  - spec/fixtures/import_test/lib/test/foo.rb
@@ -258,9 +279,15 @@ test_files:
258
279
  - spec/fixtures/lazytest/lib/test/models/book.rb
259
280
  - spec/fixtures/lazytest/lib/test/models/user.rb
260
281
  - spec/fixtures/lazytest/system/boot/bar.rb
282
+ - spec/fixtures/magic_comments/comments.rb
283
+ - spec/fixtures/manual_registration/container/foo.rb
284
+ - spec/fixtures/manual_registration/lib/test/foo.rb
285
+ - spec/fixtures/multiple_namespaced_components/multiple/level/baz.rb
286
+ - spec/fixtures/multiple_namespaced_components/multiple/level/foz.rb
261
287
  - spec/fixtures/namespaced_components/namespaced/bar.rb
262
288
  - spec/fixtures/namespaced_components/namespaced/foo.rb
263
289
  - spec/fixtures/other/config/boot/bar.rb
290
+ - spec/fixtures/other/config/boot/hell.rb
264
291
  - spec/fixtures/other/lib/test/dep.rb
265
292
  - spec/fixtures/other/lib/test/foo.rb
266
293
  - spec/fixtures/other/lib/test/models.rb
@@ -278,11 +305,14 @@ test_files:
278
305
  - spec/fixtures/test/system/boot/bar.rb
279
306
  - spec/fixtures/test/system/boot/client.rb
280
307
  - spec/fixtures/test/system/boot/db.rb
308
+ - spec/fixtures/test/system/boot/hell.rb
281
309
  - spec/fixtures/test/system/boot/logger.rb
282
310
  - spec/fixtures/umbrella/system/boot/db.rb
283
311
  - spec/integration/boot_spec.rb
312
+ - spec/integration/container/lazy_loading/manual_registration_spec.rb
284
313
  - spec/integration/import_spec.rb
285
314
  - spec/spec_helper.rb
315
+ - spec/unit/auto_registrar/configuration_spec.rb
286
316
  - spec/unit/component_spec.rb
287
317
  - spec/unit/container/auto_register_spec.rb
288
318
  - spec/unit/container/finalize_spec.rb
@@ -290,3 +320,4 @@ test_files:
290
320
  - spec/unit/container/injector_spec.rb
291
321
  - spec/unit/container_spec.rb
292
322
  - spec/unit/loader_spec.rb
323
+ - spec/unit/magic_comments_parser_spec.rb
data/.rubocop.yml DELETED
@@ -1,34 +0,0 @@
1
- # Generated by `rubocop --auto-gen-config`
2
- inherit_from: .rubocop_todo.yml
3
-
4
- # No need to handle LoadError in spec helper
5
- Lint/HandleExceptions:
6
- Exclude:
7
- - spec/spec_helper.rb
8
-
9
- Style/FileName:
10
- Exclude:
11
- - 'lib/dry-component.rb'
12
-
13
- # Documentation checked by Inch CI
14
- Style/Documentation:
15
- Enabled: false
16
-
17
- # Ain't nobody got time for that!
18
- Style/MethodCallParentheses:
19
- Enabled: false
20
-
21
- Style/Lambda:
22
- Enabled: false
23
-
24
- Style/BlockDelimiters:
25
- Enabled: false
26
-
27
- Style/LambdaCall:
28
- EnforcedStyle: braces
29
-
30
- Style/MultilineBlockChain:
31
- Enabled: false
32
-
33
- Style/StabbyLambdaParentheses:
34
- EnforcedStyle: require_no_parentheses
data/.rubocop_todo.yml DELETED
@@ -1,26 +0,0 @@
1
- # This configuration was generated by
2
- # `rubocop --auto-gen-config`
3
- # on 2015-12-23 21:42:26 +0000 using RuboCop version 0.35.1.
4
- # The point is for the user to remove these configuration records
5
- # one by one as the offenses are removed from the code base.
6
- # Note that changes in the inspected code, or installation of new
7
- # versions of RuboCop, may require this file to be generated again.
8
-
9
- # Offense count: 1
10
- Metrics/AbcSize:
11
- Max: 25
12
-
13
- # Offense count: 1
14
- # Configuration parameters: CountComments.
15
- Metrics/ClassLength:
16
- Max: 130
17
-
18
- # Offense count: 1
19
- # Configuration parameters: AllowURI, URISchemes.
20
- Metrics/LineLength:
21
- Max: 84
22
-
23
- # Offense count: 2
24
- # Configuration parameters: CountComments.
25
- Metrics/MethodLength:
26
- Max: 14