Class: Dry::System::Container
- Inherits:
-
Object
- Object
- Dry::System::Container
- Extended by:
- Configurable, Container::Mixin, Plugins
- Defined in:
- lib/dry/system/container.rb,
lib/dry/system/stubs.rb
Overview
Abstract container class to inherit from
Container class is treated as a global registry with all system components. Container can also import dependencies from other containers, which is useful in complex systems that are split into sub-systems.
Container can be finalized, which triggers loading of all the defined components within a system, after finalization it becomes frozen. This typically happens in cases like booting a web application.
Before finalization, Container can lazy-load components on demand. A component can be a simple class defined in a single file, or a complex component which has init/start/stop lifecycle, and it’s defined in a boot file. Components which specify their dependencies using Import module can be safely required in complete isolation, and Container will resolve and load these dependencies automatically.
Furthermore, Container supports auto-registering components based on dir/file naming conventions. This reduces a lot of boilerplate code as all you have to do is to put your classes under configured directories and their instances will be automatically registered within a container.
Every container needs to be configured with following settings:
-
‘:name` - a unique container identifier
-
‘:root` - a system root directory (defaults to `pwd`)
-
‘:system_dir` - directory name relative to root, where bootable components
can be defined in `boot` dir this defaults to `system`
Defined Under Namespace
Modules: Stubs
Class Method Summary collapse
- .after(event, &block) ⇒ Object private
-
.auto_register!(dir) { ... } ⇒ self
Auto-registers components from the provided directory.
- .auto_registrar ⇒ Object private
-
.boot(name, **opts, &block) ⇒ self
Registers finalization function for a bootable component.
- .boot_external(identifier, from:, key: nil, namespace: nil, &block) ⇒ Object private
- .boot_local(identifier, namespace: nil, &block) ⇒ Object private
- .boot_path ⇒ Object private
- .booter ⇒ Object private
- .component(identifier, **options) ⇒ Object private
-
.configure(&block) ⇒ self
Configures the container.
-
.enable_stubs! ⇒ Object
Enables stubbing container’s components.
-
.finalize!(freeze: true) {|_self| ... } ⇒ self
Finalizes the container.
-
.finalized? ⇒ TrueClass, FalseClass
Return if a container was finalized.
- .hooks ⇒ Object private
-
.import(other) ⇒ Object
Registers another container for import.
- .importer ⇒ Object private
- .inherited(klass) ⇒ Object private
-
.init(name) ⇒ self
Boots a specific component but calls only ‘init` lifecycle trigger.
-
.injector(options = { strategies: strategies }) ⇒ Object
Builds injector for this container.
-
.key?(key) ⇒ Boolean
Check if identifier is registered.
- .load_component(key, &block) ⇒ Object private
- .load_paths ⇒ Object private
-
.load_paths!(*dirs) ⇒ self
Sets load paths relative to the container’s root dir.
- .load_registrations!(name) ⇒ Object
- .manual_registrar ⇒ Object private
-
.registered?(key) ⇒ Boolean
Whether a
key
is registered (doesn’t trigger loading). - .require_component(component) ⇒ Object private
-
.require_from_root(*paths) ⇒ Object
Requires one or more files relative to the container’s root.
-
.require_path(path) ⇒ Object
private
Allows subclasses to use a different strategy for required files.
- .resolve(key, &block) ⇒ Object
-
.root ⇒ Pathname
Returns container’s root path.
- .shutdown! ⇒ Object
-
.start(name) ⇒ self
Boots a specific component.
-
.stop(name) ⇒ self
Stop a specific component but calls only ‘stop` lifecycle trigger.
- .strategies(value = nil) ⇒ Object
Methods included from Plugins
enabled_plugins, inherited, loaded_dependencies, register, registry, use
Class Method Details
.after(event, &block) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
644 645 646 |
# File 'lib/dry/system/container.rb', line 644 def after(event, &block) hooks[event] << block end |
.auto_register!(dir) { ... } ⇒ self
Auto-registers components from the provided directory
Typically you want to configure auto_register directories, and it will work automatically. Use this method in cases where you want to have an explicit way where some components are auto-registered, or if you want to exclude some components from being auto-registered
448 449 450 451 |
# File 'lib/dry/system/container.rb', line 448 def auto_register!(dir, &block) auto_registrar.(dir, &block) self end |
.auto_registrar ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
568 569 570 |
# File 'lib/dry/system/container.rb', line 568 def auto_registrar @auto_registrar ||= config.auto_registrar.new(self) end |
.boot(name, **opts, &block) ⇒ self
Registers finalization function for a bootable component
By convention, boot files for components should be placed in ‘%system_dir/boot` and they will be loaded on demand when components are loaded in isolation, or during finalization process.
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
# File 'lib/dry/system/container.rb', line 231 def boot(name, **opts, &block) if components.key?(name) raise DuplicatedComponentKeyError, <<-STR Bootable component #{name.inspect} was already registered STR end component = if opts[:from] boot_external(name, **opts, &block) else boot_local(name, **opts, &block) end components[name] = component end |
.boot_external(identifier, from:, key: nil, namespace: nil, &block) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
250 251 252 253 254 255 256 257 258 |
# File 'lib/dry/system/container.rb', line 250 def boot_external(identifier, from:, key: nil, namespace: nil, &block) component = System.providers[from].component( identifier, key: key, namespace: namespace, finalize: block, container: self ) booter.register_component(component) component end |
.boot_local(identifier, namespace: nil, &block) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
261 262 263 264 265 266 267 268 269 |
# File 'lib/dry/system/container.rb', line 261 def boot_local(identifier, namespace: nil, &block) component = Components::Bootable.new( identifier, container: self, namespace: namespace, &block ) booter.register_component(component) component end |
.boot_path ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
563 564 565 |
# File 'lib/dry/system/container.rb', line 563 def boot_path root.join("#{config.system_dir}/boot") end |
.booter ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
558 559 560 |
# File 'lib/dry/system/container.rb', line 558 def booter @booter ||= config.booter.new(boot_path) end |
.component(identifier, **options) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
583 584 585 586 587 588 589 590 591 592 593 594 595 596 |
# File 'lib/dry/system/container.rb', line 583 def component(identifier, **) if (component = booter.components.detect { |c| c.identifier == identifier }) component else Component.new( identifier, loader: config.loader, namespace: config.default_namespace, separator: config.namespace_separator, inflector: config.inflector, ** ) end end |
.configure(&block) ⇒ self
Configures the container
114 115 116 117 118 119 |
# File 'lib/dry/system/container.rb', line 114 def configure(&block) super(&block) load_paths!(config.system_dir) hooks[:configure].each { |hook| instance_eval(&hook) } self end |
.enable_stubs! ⇒ Object
Enables stubbing container’s components
28 29 30 31 32 |
# File 'lib/dry/system/stubs.rb', line 28 def self.enable_stubs! super extend ::Dry::System::Container::Stubs self end |
.finalize!(freeze: true) {|_self| ... } ⇒ self
Finalizes the container
This triggers importing components from other containers, booting registered components and auto-registering components. It should be called only in places where you want to finalize your system as a whole, ie when booting a web application
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/dry/system/container.rb', line 309 def finalize!(freeze: true, &block) return self if finalized? yield(self) if block importer.finalize! booter.finalize! manual_registrar.finalize! auto_registrar.finalize! @__finalized__ = true self.freeze if freeze self end |
.finalized? ⇒ TrueClass, FalseClass
Return if a container was finalized
276 277 278 |
# File 'lib/dry/system/container.rb', line 276 def finalized? @__finalized__.equal?(true) end |
.hooks ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
649 650 651 |
# File 'lib/dry/system/container.rb', line 649 def hooks @hooks ||= Hash.new { |h, k| h[k] = [] } end |
.import(other) ⇒ Object
Registers another container for import
147 148 149 150 151 152 153 154 155 156 |
# File 'lib/dry/system/container.rb', line 147 def import(other) case other when Hash then importer.register(other) when Dry::Container::Namespace then super else raise ArgumentError, <<-STR +other+ must be a hash of names and systems, or a Dry::Container namespace STR end end |
.importer ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
578 579 580 |
# File 'lib/dry/system/container.rb', line 578 def importer @importer ||= config.importer.new(self) end |
.inherited(klass) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
654 655 656 657 658 659 660 661 662 663 664 |
# File 'lib/dry/system/container.rb', line 654 def inherited(klass) new_hooks = Container.hooks.dup hooks.each do |event, blocks| new_hooks[event].concat(blocks) new_hooks[event].concat(klass.hooks[event]) end klass.instance_variable_set(:@hooks, new_hooks) super end |
.init(name) ⇒ self
Boots a specific component but calls only ‘init` lifecycle trigger
This way of booting is useful in places where a heavy dependency is needed but its started environment is not required
355 356 357 358 |
# File 'lib/dry/system/container.rb', line 355 def init(name) booter.init(name) self end |
.injector(options = { strategies: strategies }) ⇒ Object
Builds injector for this container
An injector is a useful mixin which injects dependencies into automatically defined constructor.
478 479 480 |
# File 'lib/dry/system/container.rb', line 478 def injector( = { strategies: strategies }) Dry::AutoInject(self, ) end |
.key?(key) ⇒ Boolean
Check if identifier is registered. If not, try to load the component
543 544 545 546 547 548 549 550 |
# File 'lib/dry/system/container.rb', line 543 def key?(key) if finalized? registered?(key) else registered?(key) || resolve(key) { return false } true end end |
.load_component(key, &block) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 |
# File 'lib/dry/system/container.rb', line 621 def load_component(key, &block) return self if registered?(key) component(key).tap do |component| if component.boot? booter.start(component) else root_key = component.root_key if (bootable_dep = component(root_key)).boot? booter.start(bootable_dep) elsif importer.key?(root_key) load_imported_component(component.namespaced(root_key)) end load_local_component(component, &block) unless registered?(key) end end self end |
.load_paths ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
553 554 555 |
# File 'lib/dry/system/container.rb', line 553 def load_paths @load_paths ||= [] end |
.load_paths!(*dirs) ⇒ self
Sets load paths relative to the container’s root dir
396 397 398 399 400 401 402 403 404 |
# File 'lib/dry/system/container.rb', line 396 def load_paths!(*dirs) dirs.map(&root.method(:join)).each do |path| next if load_paths.include?(path) load_paths << path $LOAD_PATH.unshift(path.to_s) end self end |
.load_registrations!(name) ⇒ Object
407 408 409 410 |
# File 'lib/dry/system/container.rb', line 407 def load_registrations!(name) manual_registrar.(name) self end |
.manual_registrar ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
573 574 575 |
# File 'lib/dry/system/container.rb', line 573 def manual_registrar @manual_registrar ||= config.manual_registrar.new(self) end |
.registered?(key) ⇒ Boolean
Whether a key
is registered (doesn’t trigger loading)
528 |
# File 'lib/dry/system/container.rb', line 528 alias_method :registered?, :key? |
.require_component(component) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
599 600 601 602 603 604 605 606 607 |
# File 'lib/dry/system/container.rb', line 599 def require_component(component) return if registered?(component.identifier) raise FileNotFoundError, component unless component.file_exists?(load_paths) require_path(component.path) yield end |
.require_from_root(*paths) ⇒ Object
Requires one or more files relative to the container’s root
494 495 496 497 498 499 500 |
# File 'lib/dry/system/container.rb', line 494 def require_from_root(*paths) paths.flat_map { |path| path.to_s.include?('*') ? ::Dir[root.join(path)].sort : root.join(path) }.each { |path| require path.to_s } end |
.require_path(path) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Allows subclasses to use a different strategy for required files.
E.g. apps that use ‘ActiveSupport::Dependencies::Loadable#require_dependency` will override this method to allow container managed dependencies to be reloaded for non-finalized containers.
616 617 618 |
# File 'lib/dry/system/container.rb', line 616 def require_path(path) require path end |
.resolve(key, &block) ⇒ Object
521 522 523 524 525 |
# File 'lib/dry/system/container.rb', line 521 def resolve(key, &block) load_component(key, &block) unless finalized? super end |
.root ⇒ Pathname
Returns container’s root path
516 517 518 |
# File 'lib/dry/system/container.rb', line 516 def root config.root end |
.shutdown! ⇒ Object
375 376 377 378 |
# File 'lib/dry/system/container.rb', line 375 def shutdown! booter.shutdown self end |
.start(name) ⇒ self
Boots a specific component
As a result, ‘init` and `start` lifecycle triggers are called
337 338 339 340 |
# File 'lib/dry/system/container.rb', line 337 def start(name) booter.start(name) self end |
.stop(name) ⇒ self
Stop a specific component but calls only ‘stop` lifecycle trigger
370 371 372 373 |
# File 'lib/dry/system/container.rb', line 370 def stop(name) booter.stop(name) self end |
.strategies(value = nil) ⇒ Object
90 91 92 93 94 95 96 |
# File 'lib/dry/system/container.rb', line 90 def strategies(value = nil) if value @strategies = value else @strategies ||= Dry::AutoInject::Strategies end end |