Releases: dry-rb/dry-system
v0.26.0
v0.25.0
v0.24.0
v0.23.0
This is a major overhaul of bootable components (now known as “Providers”), and brings major advancements to other areas, including container imports and exports.
Deprecations are in place for otherwise breaking changes to commonly used parts of dry-system, though some breaking changes remain.
This prepares the way for dry-system 1.0, which will be released in the coming months.
Added
-
Containers can configure specific components for export using
config.exports
(@timriley in #209).class MyContainer < Dry::System::Container configure do |config| config.exports = %w[component_a component_b] end end
Containers importing another container with configured exports will import only those components.
When importing a specific set of components (see the note in the “Changed” section below), only those components whose keys intersect with the configured exports will be imported.
-
A
:zeitwerk
plugin, to set up Zeitwerk and integrate it with your container configuration (@ianks and @timriley in #197, #222, 13f8c87, #223)This makes it possible to enable Zeitwerk with a one-liner:
class MyContainer < Dry::System::Container use :zeitwerk configure do |config| config.component_dirs.add "lib" # ... end end
The plugin makes a
Zeitwerk::Loader
instance available atconfig.autoloader
, and then in an after-:configure
hook, the plugin will set up the loader to work with all of your configured component dirs and their namespaces. It will also enable theDry::System::Loader::Autoloading
loader for all component dirs, plus disable those dirs from being added to the$LOAD_PATH
.The plugin accepts the following options:
loader:
- (optional) to use a pre-initialized loader, if required.run_setup:
- (optional) a bool to determine whether to runZeitwerk::Loader#setup
as part of the after-:configure
hook. This may be useful to disable in advanced cases when integrating with an externally managed loader.eager_load:
- (optional) a bool to determine whether to runZeitwerk::Loader#eager_load
as part of an after-:finalize
hook. When not provided, it will default to true if the:env
plugin is enabled and the env is set to:production
.debug:
- (optional) a bool to set whether Zeitwerk should log to$stdout
.
-
New
Identifier#end_with?
andIdentifier#include?
predicates (@timriley in #219)These are key segment-aware predicates that can be useful when checking components as part of container configuration.
identifier.key # => "articles.operations.create" identifier.end_with?("create") # => true identifier.end_with?("operations.create") # => true identifier.end_with?("ate") # => false, not a whole segment identifier.end_with?("nope") # => false, not part of the key at all identifier.include?("operations") # => true identifier.include?("articles.operations") # => true identifier.include?("operations.create") # => true identifier.include?("article") # false, not a whole segment identifier.include?("update") # => false, not part of the key at all
-
An
instance
setting for component dirs allows simpler per-dir control over component instantiation (@timriley in #215)This optional setting should be provided a proc that receives a single
Dry::System::Component
instance as an argument, and should return the instance for the given component.configure do |config| config.component_dirs.add "lib" do |dir| dir.instance = proc do |component| if component.identifier.include?("workers") # Register classes for jobs component.loader.constant(component) else # Otherwise register regular instances per default loader component.loader.call(component) end end end end
For complete control of component loading, you should continue to configure the component dir’s
loader
instead. -
A new
ComponentNotLoadableError
error and helpful message is raised when resolving a component and an unexpected class is defined in the component’s source file (@cllns in #217).The error shows expected and found class names, and inflector configuration that may be required in the case of class names containing acronyms.
Fixed
-
Registrations made in providers (by calling
register
inside a provider step) have all their registration options preserved (such as a block-based registration, or thememoize:
option) when having their registration merged into the target container after the provider lifecycle steps complete (@timriley in #212). -
Providers can no longer implicitly re-start themselves while in the process of starting and cause an infinite loop (@timriley #213).
This was possible before when a provider resolved a component from the target container that auto-injected dependencies with container keys sharing the same base key as the provider name.
Changed
-
“Bootable components” (also referred to in some places simply as “components”) have been renamed to “Providers” (@timriley in #200).
Register a provider with
Dry::System::Container.register_provider
(Dry::System::Container.boot
has been deprecated):MyContainer.register_provider(:mailer) do # ... end
-
Provider
init
lifecycle step has been deprecated and renamed toprepare
(@timriley in #200).MyContainer.reigster_provider(:mailer) do # Rename `init` to `prepare` prepare do require "some/third_party/mailer" end end
-
Provider behavior is now backed by a class per provider, known as the “Provider source” (@timriley in #202).
The provider source class is created for each provider as a subclass of
Dry::System::Provider::Source
.You can still register simple providers using the block-based DSL, but the class backing means you can share state between provider steps using regular instance variables:
MyContainer.reigster_provider(:mailer) do prepare do require "some/third_party/mailer" @some_config = ThirdParty::Mailer::Config.new end start do # Since the `prepare` step will always run before start, we can access # @some_config here register "mailer", ThirdParty::Mailer.new(@some_config) end end
Inside this
register_provider
block,self
is the source subclass itself, and inside each of the step blocks (i.e.prepare do
),self
will be the instance of that provider source.For more complex providers, you can define your own source subclass and register it directly with the
source:
option forregister_provider
. This allows you to more readily use standard arrangements for factoring your logic within a class, such as extraction to another method:MyContainer.register_provider(:mailer, source: Class.new(Dry::System::Provider::Source) { # The provider lifecycle steps are ordinary methods def prepare end def start mailer = some_complex_logic_to_build_the_mailer(some: "config") register(:mailer, mailer) end private def some_complex_logic_to_build_the_mailer(**options) # ... end })
-
The block argument to
Dry::System::Container.register_provider
(previously.boot
) has been deprecated. (@timriley in #202).This argument was used to give you access to the provider's target container (i.e. the container on which you were registering the provider).
To access the target container, you can use
#target_container
(or#target
as a convenience alias) instead.You can also access the provider's own container (which is where the provider's components are registered when you call
register
directly inside a provider step) as#provider_container
(or#container
as a convenience alias). -
use(provider_name)
inside a provider step has been deprecated. Usetarget_container.start(provider_name)
instead (@timriley in #211 and #224)Now that you can access
target_container
consistently within all provider steps, you can use it to also start any other providers as you require without any special additional method. This also allows you to invoke other provider lifecycle steps, liketarget_container.prepare(provider_name)
. -
method_missing
-based delegation within providers to target container registrations has been removed (BREAKING) (@timriley in #202)Delegation to registrations with the provider's own container has been kept, since it can be a convenient way to access registrations made in a prior lifecycle step:
MyContainer.register_provider(:mailer, namespace: true) do prepare do register :config, "mailer config here" end start do config # => "mailer config here" end end
-
The previous "external component" and "provider" concepts have been renamed to "external provider sources", in keeping with the new provider terminology outlined above (@timriley in #200 and #202).
You can register a collection of external provider sources defined in their own source files via
Dry::System.register_provider_sources
(Dry::System.register_provider
has been deprecated):require "dry/system" Dry::System.register_provider_sources(path)
You can register an individual external provider source via
Dry::System.register_provider_source
(Dry::System.register_component
has been deprecated):Dry::System.register_provider_source(:something, group: :my_gem) do start do # ... end end
Just like providers, you can also register a class as an external provider source:
module MyGem class MySource < Dry::System::Provider::Source def start # ... end end end Dry::System.register_provider_source(:somet...
v0.22.0
Added
- Expanded public interfaces for
Dry::System::Config::ComponentDirs
andDry::System::Config::Namespaces
to better support programmatic construction and inspection of these configs (@timriley in #195)
Changed
v0.21.0
Added
- Added component dir namespaces as a way to specify multiple, ordered, independent namespace rules within a given component dir. This replaces and expands upon the namespace support we previously provided via the singular
default_namespace
component dir setting (@timriley in #181)
Changed
-
default_namespace
setting on component dirs has been deprecated. Add a component dir namespace instead, e.g. instead of:# Inside Dry::System::Container.configure config.component_dirs.add "lib" do |dir| dir.default_namespace = "admin" end
Add this:
config.component_dirs.add "lib" do |dir| dir.namespaces.add "admin", key: nil end
-
Dry::System::Component#path
has been removed and replaced byComponent#require_path
andComponent#const_path
(@timriley in #181) -
Unused
Dry::System::FileNotFoundError
andDry::System::InvalidComponentIdentifierTypeError
errors have been removed (@timriley in #194)
v0.20.0
Fixed
- Fixed dependency graph plugin to work with internal changes introduced in 0.19.0 (@wuarmin in #173)
- Fixed behavior of
Dry::System::Identifier#start_with?
for components identified by a single segment, or if all matching segments are provided (@wuarmin in #177) - Fixed compatibility of
finalize!
signature provided inContainer::Stubs
(@mpokrywka in #178)
Changed
v0.19.2
v0.19.1
v0.19.0
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.
Added
-
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.
-
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
-
[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)
Changed
- 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)