Releases: dry-rb/dry-system
v1.0.1
v1.0.0
Fixed
Changed
- This version uses dry-core 1.0 and dry-configurable 1.0 (@solnic + @flash-gordon)
- Raise error on import after finalize (via #254) (@timriley + @tak1n)
- Validate settings even if loader does not set value (via #246) (@oeoeaio)
- Remove all deprecated functionality and deprecation messages (via #255) (@timriley)
- Use main dry/monitor entrypoint for autoloading (via #257) (@timriley)
- Use dry-configurable 1.0 (via 43c7909) (@flash-gordon)
- Use dry-core 1.0 (via 3d0cf95) (@flash-gordon)
- Remove dry-container dependency and update to use
Dry::Core::Container
(via 2b76554) (@solnic)
v1.0.0.rc1
Changed
- This version uses dry-core 1.0 and dry-configurable 1.0 (@solnic + @flash-gordon)
v0.27.2
v0.27.1
v0.27.0
Changed
- Use zeitwerk for auto-loading dry-system (@flash-gordon + @solnic)
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...