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.
- .resolve(key) ⇒ Object
-
.root ⇒ Pathname
Returns container’s root path.
-
.start(name) ⇒ self
Boots a specific component.
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.
570 571 572 |
# File 'lib/dry/system/container.rb', line 570 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 been auto-registered
409 410 411 412 |
# File 'lib/dry/system/container.rb', line 409 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.
504 505 506 |
# File 'lib/dry/system/container.rb', line 504 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.
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/dry/system/container.rb', line 217 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.
235 236 237 238 239 240 241 242 243 |
# File 'lib/dry/system/container.rb', line 235 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.
246 247 248 249 250 251 252 |
# File 'lib/dry/system/container.rb', line 246 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.
499 500 501 |
# File 'lib/dry/system/container.rb', line 499 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.
494 495 496 |
# File 'lib/dry/system/container.rb', line 494 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.
519 520 521 522 523 524 525 526 527 528 529 530 531 |
# File 'lib/dry/system/container.rb', line 519 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, **, ) end end |
.configure(&block) ⇒ self
Configures the container
102 103 104 105 106 107 |
# File 'lib/dry/system/container.rb', line 102 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
292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/dry/system/container.rb', line 292 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 end |
.finalized? ⇒ TrueClass, FalseClass
Return if a container was finalized
259 260 261 |
# File 'lib/dry/system/container.rb', line 259 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.
575 576 577 |
# File 'lib/dry/system/container.rb', line 575 def hooks @__hooks__ ||= Hash.new { |h, k| h[k] = [] } end |
.import(other) ⇒ Object
Registers another container for import
135 136 137 138 139 140 141 142 |
# File 'lib/dry/system/container.rb', line 135 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.
514 515 516 |
# File 'lib/dry/system/container.rb', line 514 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.
580 581 582 583 584 585 586 587 588 589 590 |
# File 'lib/dry/system/container.rb', line 580 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
337 338 339 340 |
# File 'lib/dry/system/container.rb', line 337 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.
439 440 441 |
# File 'lib/dry/system/container.rb', line 439 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.
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 |
# File 'lib/dry/system/container.rb', line 547 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)) else 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.
489 490 491 |
# File 'lib/dry/system/container.rb', line 489 def load_paths @load_paths ||= [] end |
.load_paths!(*dirs) ⇒ self
Sets load paths relative to the container’s root dir
358 359 360 361 362 363 364 365 |
# File 'lib/dry/system/container.rb', line 358 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
368 369 370 371 |
# File 'lib/dry/system/container.rb', line 368 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.
509 510 511 |
# File 'lib/dry/system/container.rb', line 509 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.
534 535 536 537 538 539 540 541 542 543 544 |
# File 'lib/dry/system/container.rb', line 534 def require_component(component) return if key?(component.identifier) unless component.file_exists?(load_paths) raise FileNotFoundError, component end require component.path yield end |
.require_from_root(*paths) ⇒ Object
Requires one or more files relative to the container’s root
455 456 457 458 459 460 461 |
# File 'lib/dry/system/container.rb', line 455 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 |
.resolve(key) ⇒ Object
482 483 484 485 486 |
# File 'lib/dry/system/container.rb', line 482 def resolve(key) load_component(key) unless finalized? super end |
.root ⇒ Pathname
Returns container’s root path
477 478 479 |
# File 'lib/dry/system/container.rb', line 477 def root config.root end |
.start(name) ⇒ self
Boots a specific component
As a result, ‘init` and `start` lifecycle triggers are called
319 320 321 322 |
# File 'lib/dry/system/container.rb', line 319 def start(name) booter.start(name) self end |