This release marks a huge step forward for dry-system, bringing support for Zeitwerk and other autoloaders, plus clearer configuration and improved consistency around component resolution for both finalized and lazy loading containers. Read the announcement post for a high-level tour of the new features.
-
New
component_dirs
setting onDry::System::Container
, which must be used for specifying the directories which dry-system will search for component source files.Each added component dir is relative to the container's
root
, and can have its own set of settings configured:class MyApp::Container < Dry::System::Container configure do |config| config.root = __dir__ # Defaults for all component dirs can be configured separately config.component_dirs.auto_register = true # default is already true # Component dirs can be added and configured independently config.component_dirs.add "lib" do |dir| dir.add_to_load_path = true # defaults to true dir.default_namespace = "my_app" end # All component dir settings are optional. Component dirs relying on default # settings can be added like so: config.component_dirs.add "custom_components" end end
The following settings are available for configuring added
component_dirs
:auto_register
, a boolean, or a proc accepting aDry::System::Component
instance and returning a truthy or falsey value. Providing a proc allows an auto-registration policy to apply on a per-component basisadd_to_load_path
, a booleandefault_namespace
, a string representing the leading namespace segments to be stripped from the component's identifier (given the identifier is derived from the component's fully qualified class name)loader
, a custom replacement for the defaultDry::System::Loader
to be used for the component dirmemoize
, a boolean, to enable/disable memoizing all components in the directory, or a proc accepting aDry::System::Component
instance and returning a truthy or falsey value. Providing a proc allows a memoization policy to apply on a per-component basis
All component dir settings are optional.
(@timriley in #155, #157, and #162)
-
A new autoloading-friendly
Dry::System::Loader::Autoloading
is available, which is tested to work with Zeitwerk 🎉Configure this on the container (via a component dir
loader
setting), and the loader will no longerrequire
any components, instead allowing missing constant resolution to trigger the loading of the required file.This loader presumes an autoloading system like Zeitwerk has already been enabled and appropriately configured.
A recommended setup is as follows:
require "dry/system/container" require "dry/system/loader/autoloading" require "zeitwerk" class MyApp::Container < Dry::System::Container configure do |config| config.root = __dir__ config.component_dirs.loader = Dry::System::Loader::Autoloading config.component_dirs.add_to_load_path = false config.component_dirs.add "lib" do |dir| # ... end end end loader = Zeitwerk::Loader.new loader.push_dir MyApp::Container.config.root.join("lib").realpath loader.setup
(@timriley in #153)
-
[BREAKING]
Dry::System::Component
instances (which users of dry-system will interact with via custom loaders, as well as via theauto_register
andmemoize
component dir settings described above) now return aDry::System::Identifier
from their#identifier
method. The raw identifier string may be accessed via the identifier's own#key
or#to_s
methods.Identifier
also provides a helpful namespace-aware#start_with?
method for returning whether the identifier begins with the provided namespace(s) (@timriley in #158)
- Components with
# auto_register: false
magic comments in their source files are now properly ignored when lazy loading (@timriley in #155) # memoize: true
and# memoize: false
magic comments at top of component files are now respected (@timriley in #155)- [BREAKING]
Dry::System::Container.load_paths!
has been renamed to.add_to_load_path!
. This method now exists as a mere convenience only. Calling this method is no longer required for any configuredcomponent_dirs
; these are now added to the load path automatically (@timriley in #153 and #155) - [BREAKING]
auto_register
container setting has been removed. Configured directories to be auto-registered by addingcomponent_dirs
instead (@timriley in #155) - [BREAKING]
default_namespace
container setting has been removed. Set it when addingcomponent_dirs
instead (@timriley in #155) - [BREAKING]
loader
container setting has been nested undercomponent_dirs
, now available ascomponent_dirs.loader
to configure a default loader for all component dirs, as well as on individual component dirs when being added (@timriley in #162) - [BREAKING]
Dry::System::ComponentLoadError
is no longer raised when a component could not be lazy loaded; this was only raised in a single specific failure condition. Instead, aDry::Container::Error
is raised in all cases of components failing to load (@timriley in #155) - [BREAKING]
Dry::System::Container.auto_register!
has been removed. Configurecomponent_dirs
instead. (@timriley in #157) - [BREAKING] The
Dry::System::Loader
interface has changed. It is now a static interface, no longer initialized with a component. The component is instead passed to each method as an argument:.require!(component)
,.call(component, *args)
,.constant(component)
(@timriley in #157) - [BREAKING]
Dry::System::Container.require_path
has been removed. Provide custom require behavior by configuring your ownloader
(@timriley in #153)
- Made
Booter#boot_files
a public method again, since it was required by dry-rails (@timriley)
-
New
bootable_dirs
setting onDry::System::Container
, which accepts paths to multiple directories for looking up bootable component files. (@timriley in PR #151)For each entry in the
bootable_dirs
array, relative directories will be appended to the container'sroot
, and absolute directories will be left unchanged.When searching for bootable files, the first match will win, and any subsequent same-named files will not be loaded. In this way, the
bootable_dirs
act similarly to the$PATH
in a shell environment.
- Works with the latest dry-configurable version (issue #141) (@solnic)
- Depends on dry-configurable
=> 0.11.1
now (@solnic)
- Plugins can now define their own settings which are available in the
before(:configure)
hook (@solnic) - Dependency on dry-configurable was bumped to
~> 0.11
(@solnic)
- New hook -
before(:configure)
which a plugin should use if it needs to declare new settings (@solnic)
# in your plugin code
before(:configure) { setting :my_new_setting }
after(:configure) { config.my_new_setting = "awesome" }
- Centralize error definitions in
lib/dry/system/errors.rb
(@cgeorgii) - All built-in plugins use
before(:configure)
now to declare their settings (@solnic)
- Use
Kernel.require
explicitly to avoid issues with monkey-patchedrequire
from ActiveSupport (@solnic)
- Misspelled plugin name raises meaningful error (issue #132) (@cgeorgii)
- Fail fast if auto_registrar config contains incorrect path (@cutalion)
- More keyword warnings (flash-gordon)
- Fixed keyword warnings reported by Ruby 2.7 (flash-gordon)
- Duplicates in
Dry::System::Plugins.loaded_dependencies
(AMHOL)
Container.resolve
accepts and optional block parameter which will be called if component cannot be found. This makes dry-system consistent with dry-container 0.7.2 (flash-gordon)App.resolve('missing.dep') { :fallback } # => :fallback
- [BREAKING]
Container.key?
triggers lazy-loading for not finalized containers. If component wasn't found it returnsfalse
without raising an error. This is a breaking change, if you seek the previous behavior, useContainer.registered?
(flash-gordon)
- Compatibility with dry-struct 1.0 and dry-types 1.0 (flash-gordon)
- [BREAKING]
:decorate
plugin was moved from dry-system to dry-container (available in 0.7.0+). To upgrade removeuse :decorate
and changedecorate
calls fromdecorate(key, decorator: something)
todecorate(key, with: something)
(flash-gordon) - [internal] Compatibility with dry-struct 0.7.0 and dry-types 0.15.0
- Support for stopping bootable components with
Container.stop(component_name)
(GustavoCaso)
- When using a non-finalized container, you can now resolve multiple different container objects registered using the same root key as a bootable component (timriley)
-
You can now set a custom inflector on the container level. As a result, the
Loader
's constructor accepts two arguments:path
andinflector
, update your custom loaders accordingly (flash-gordon)class MyContainer < Dry::System::Container configure do |config| config.inflector = Dry::Inflector.new do |inflections| inflections.acronym('API') end end end
- A helpful error will be raised if an invalid setting value is provided (GustavoCaso)
- When using setting plugin, will use default values from types (GustavoCaso)
- Minimal supported ruby version was bumped to
2.3
(flash-gordon) dry-struct
was updated to~> 0.5
(flash-gordon)
- Default namespace no longer breaks resolving dependencies with identifier that includes part of the namespace (ie
mail.mailer
) (GustavoCaso)
- Plugin dependencies are now auto-required and a meaningful error is raised when a dep failed to load (solnic)
- Plugin API (solnic)
:env
plugin which adds support for settingenv
config value (solnic):logging
plugin which adds a default logger (solnic):decorate
plugin for decorating registered objects (solnic):notifications
plugin adding pub/sub bus to containers (solnic):monitoring
plugin which addsmonitor
method for monitoring object method calls (solnic):bootsnap
plugin which adds support for bootsnap (solnic)
- [BREAKING] renamed
Container.{require=>require_from_root}
(GustavoCaso)
- Aliasing an external component works correctly (solnic)
- Manually calling
:init
will also finalize a component (solnic)
- Support for external bootable components (solnic)
- Built-in
:system
components including:settings
component (solnic)
- Lazy-loading components work when a container has
default_namespace
configured (GustavoCaso)
- [BREAKING] Improved boot DSL with support for namespacing and lifecycle before/after callbacks (solnic)
Container.enable_stubs!
calls super too, which actually addsstub
API (solnic)- Issues with lazy-loading and import in stub mode are gone (solnic)
Container.enable_stubs!
for test environments which enables stubbing components (GustavoCaso)
- Component identifiers can now include same name more than once ie
foo.stuff.foo
(GustavoCaso) Container#boot!
was renamed toContainer#start
(davydovanton)Container#boot
was renamed toContainer#init
(davydovanton)
- Accept string values for Container's
root
config (timriley)
-
Added
manual_registrar
container setting (along with defaultManualRegistrar
implementation), andregistrations_dir
setting. These provide support for a well-established place for keeping files with manual container registrations (timriley) -
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) -
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):class MyContainer < Dry::System::Container auto_register!('lib') do |config| config.instance do |component| # custom logic for initializing a component end config.exclude do |component| # return true to skip auto-registration of the component, e.g. # component.path =~ /entities/ end end end
-
A helpful error will be raised if a bootable component's finalize block name doesn't match its boot file name (GustavoCaso)
- The
default_namespace
container setting now supports multi-level namespaces (GustavoCaso) Container.auto_register!
yields a configuration block instead of a block for returning a custom instance (see above) (GustavoCaso)Container.import
now requires an explicit local name for the imported container (e.g.import(local_name: AnotherContainer)
) (timriley)
- Lazy load components as they are resolved, rather than on injection (timriley)
- Perform registration even though component already required (blelump)
- Undefined locals or method calls will raise proper exceptions in Lifecycle DSL (aradunovic)
for multi-container setups. As part of this release dry-system
has been renamed to dry-system
.
- Boot DSL with:
- Lifecycle triggers:
init
,start
andstop
(solnic) use
method which auto-boots a dependency and makes it available in the booting context (solnic)
- Lifecycle triggers:
- When a component relies on a bootable component, and is being loaded in isolation, the component will be booted automatically (solnic)
- [BREAKING]
Dry::Component::Container
is nowDry::System::Container
(solnic) - [BREAKING] Configurable
loader
is now a class that accepts container's config and responds to#constant
and#instance
(solnic) - [BREAKING]
core_dir
renameda tosystem_dir
and defaults tosystem
(solnic) - [BREAKING]
auto_register!
yieldsComponent
objects (solnic)
- Return immediately from
Container.load_component
if the requested component key already exists in the container. This fixes a crash when requesting to load a manually registered component with a name that doesn't map to a filename (timriley in #24)
- Ensure file components can be loaded when they're requested for the first time using their shorthand container identifier (i.e. with the container's default namespace removed) (timriley)
- Require the 0.4.0 release of dry-auto_inject for the features below (in 0.4.0) to work properly (timriley)
-
Support for supplying a default namespace to a container, which is passed to the container's injector to allow for convenient shorthand access to registered objects in the same namespace (timriley in #20)
# Set up container with default namespace module Admin class Container < Dry::Component::Container configure do |config| config.root = Pathname.new(__dir__).join("../..") config.default_namespace = "admin" end end Import = Container.injector end module Admin class CreateUser # "users.repository" will resolve an Admin::Users::Repository instance, # where previously you had to identify it as "admin.users.repository" include Admin::Import["users.repository"] end end
-
Support for supplying to options directly to dry-auto_inject's
Builder
viaDry::Component::Container#injector(options)
. This allows you to provide dry-auto_inject customizations like your own container of injection strategies (timriley in #20) -
Support for accessing all available injector strategies, not just the defaults (e.g.
MyContainer.injector.some_custom_strategy
) (timriley in #19)
- Subclasses of
Dry::Component::Container
no longer have anInjector
constant automatically defined within them. The recommended approach is to save your own injector object to a constant, which allows you to pass options to it at the same time, e.g.MyApp::Import = MyApp::Container.injector(my_options)
(timriley in #19)
Removed two pieces that are moving to dry-web:
- Removed two pieces that are moving to dry-web:
- Removed
env
setting fromContainer
(timriley) - Removed
Dry::Component::Config
andoptions
setting fromContainer
(timriley) - Changed
Component#configure
behavior so it can be run multiple times for configuration to be applied in multiple passes (timriley)
- Fixed bug where specified auto-inject strategies were not respected (timriley)
- Component core directory is now
component/
by default (timriley) - Injector default stragegy is now whatever dry-auto_inject's default is (rather than hard-coding a particular default strategy for dry-system) (timriley)
-
Provide a dependency injector as an
Inject
constant inside any subclass ofDry::Component::Container
. This injector supports all ofdry-auto_inject
's default injection strategies, and will lazily load any dependencies as they are injected. It also supports arbitrarily switching strategies, so they can be used in different classes as required (e.g.include MyComponent::Inject.args["dep"]
) (timriley) -
Support aliased dependency names when calling the injector object (e.g.
MyComponent::Inject[foo: "my_app.foo", bar: "another.thing"]
) (timriley) -
Allow a custom dependency loader to be set on a container via its config (AMHOL)
class MyContainer < Dry::Component::Container configure do |config| # other config config.loader = MyLoader end end
Container.boot
now only makes a simplerequire
for the boot file (solnic)- Container object is passed to
Container.finalize
blocks (solnic) - Allow
Pathname
objects passed toContainer.require
(solnic) - Support lazily loading missing dependencies from imported containers (solnic)
Container.import_module
renamed to.injector
(timriley)- Default injection strategy is now
kwargs
, courtesy of the new dry-auto_inject default (timriley)
- Containers have a
name
setting (solnic) - Containers can be imported into one another (solnic)
- Container name is used to determine the name of its config file (solnic)
First public release, extracted from rodakase project