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 = {}) ⇒ Object
Builds injector for this container.
- .load_component(key) ⇒ 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
- .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) ⇒ 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.
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.
607 608 609 |
# File 'lib/dry/system/container.rb', line 607 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
432 433 434 435 |
# File 'lib/dry/system/container.rb', line 432 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.
527 528 529 |
# File 'lib/dry/system/container.rb', line 527 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.
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/dry/system/container.rb', line 219 def boot(name, opts = {}, &block) if components.key?(name) raise DuplicatedComponentKeyError, "Bootable component #{name.inspect} was already registered" end component = if opts[:from] boot_external(name, opts, &block) else boot_local(name, opts, &block) end self 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.
237 238 239 240 241 242 243 244 245 |
# File 'lib/dry/system/container.rb', line 237 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.
248 249 250 251 252 253 254 |
# File 'lib/dry/system/container.rb', line 248 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.
522 523 524 |
# File 'lib/dry/system/container.rb', line 522 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.
517 518 519 |
# File 'lib/dry/system/container.rb', line 517 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.
542 543 544 545 546 547 548 549 550 551 552 553 554 555 |
# File 'lib/dry/system/container.rb', line 542 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
104 105 106 107 108 109 |
# File 'lib/dry/system/container.rb', line 104 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
26 27 28 29 30 |
# File 'lib/dry/system/stubs.rb', line 26 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
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/dry/system/container.rb', line 294 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
261 262 263 |
# File 'lib/dry/system/container.rb', line 261 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.
612 613 614 |
# File 'lib/dry/system/container.rb', line 612 def hooks @__hooks__ ||= Hash.new { |h, k| h[k] = [] } end |
.import(other) ⇒ Object
Registers another container for import
137 138 139 140 141 142 143 144 |
# File 'lib/dry/system/container.rb', line 137 def import(other) case other when Hash then importer.register(other) when Dry::Container::Namespace then super else raise ArgumentError, "+other+ must be a hash of names and systems, or a Dry::Container namespace" 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.
537 538 539 |
# File 'lib/dry/system/container.rb', line 537 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.
617 618 619 620 621 622 623 624 625 626 627 |
# File 'lib/dry/system/container.rb', line 617 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
340 341 342 343 |
# File 'lib/dry/system/container.rb', line 340 def init(name) booter.init(name) self end |
.injector(options = {}) ⇒ Object
Builds injector for this container
An injector is a useful mixin which injects dependencies into automatically defined constructor.
462 463 464 |
# File 'lib/dry/system/container.rb', line 462 def injector( = {}) Dry::AutoInject(self, ) end |
.load_component(key) ⇒ 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.
582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 |
# File 'lib/dry/system/container.rb', line 582 def load_component(key) return self if key?(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 if !key?(key) load_local_component(component) end 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.
512 513 514 |
# File 'lib/dry/system/container.rb', line 512 def load_paths @load_paths ||= [] end |
.load_paths!(*dirs) ⇒ self
Sets load paths relative to the container’s root dir
381 382 383 384 385 386 387 388 |
# File 'lib/dry/system/container.rb', line 381 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
391 392 393 394 |
# File 'lib/dry/system/container.rb', line 391 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.
532 533 534 |
# File 'lib/dry/system/container.rb', line 532 def manual_registrar @manual_registrar ||= config.manual_registrar.new(self) end |
.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.
558 559 560 561 562 563 564 565 566 567 568 |
# File 'lib/dry/system/container.rb', line 558 def require_component(component) return if key?(component.identifier) unless component.file_exists?(load_paths) raise FileNotFoundError, component end require_path(component.path) yield end |
.require_from_root(*paths) ⇒ Object
Requires one or more files relative to the container’s root
478 479 480 481 482 483 484 |
# File 'lib/dry/system/container.rb', line 478 def require_from_root(*paths) paths.flat_map { |path| path.to_s.include?('*') ? Dir[root.join(path)] : 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.
577 578 579 |
# File 'lib/dry/system/container.rb', line 577 def require_path(path) require path end |
.resolve(key) ⇒ Object
505 506 507 508 509 |
# File 'lib/dry/system/container.rb', line 505 def resolve(key) load_component(key) unless finalized? super end |
.root ⇒ Pathname
Returns container’s root path
500 501 502 |
# File 'lib/dry/system/container.rb', line 500 def root config.root end |
.shutdown! ⇒ Object
360 361 362 363 |
# File 'lib/dry/system/container.rb', line 360 def shutdown! booter.shutdown self end |
.start(name) ⇒ self
Boots a specific component
As a result, ‘init` and `start` lifecycle triggers are called
322 323 324 325 |
# File 'lib/dry/system/container.rb', line 322 def start(name) booter.start(name) self end |
.stop(name) ⇒ self
Stop a specific component but calls only ‘stop` lifecycle trigger
355 356 357 358 |
# File 'lib/dry/system/container.rb', line 355 def stop(name) booter.stop(name) self end |