Skip to content

Releases: dry-rb/dry-system

v0.26.0

08 Oct 00:08
1f6f1a5
Compare
Choose a tag to compare

Changed

  • Update dry-configurable dependency to 0.16.0 and make internal adjustments to suit (@timriley in #249)
  • Remove now-unused concurrent-ruby gem dependency (@timriley in #250)

Compare v0.25.0...v0.26.0

v0.25.0

10 Jul 09:58
v0.25.0
Compare
Choose a tag to compare

Fixed

  • Fix incorrect type in ManifestRegistrar#finalize! (@alassek)

Changed

Compare v0.24.0...v0.25.0

v0.24.0

25 May 11:47
Compare
Choose a tag to compare

Changed

Compare v0.23.0...v0.24.0

v0.23.0

08 Feb 12:36
Compare
Choose a tag to compare

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 at config.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 the Dry::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 run Zeitwerk::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 run Zeitwerk::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? and Identifier#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 the memoize: 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 to prepare (@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 for register_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. Use target_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, like target_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...
Read more

v0.22.0

06 Jan 01:42
Compare
Choose a tag to compare

Added

  • Expanded public interfaces for Dry::System::Config::ComponentDirs and Dry::System::Config::Namespaces to better support programmatic construction and inspection of these configs (@timriley in #195)

Changed

  • Deprecated Dry::System::Config::Namespaces#root as the way to add and configure a root namespace. Use #add_root instead (@timriley in #195)
  • Allow bootsnap plugin to use bootsnap on Ruby versions up to 3.0 (pusewicz in #196)

Compare v0.21.0...v0.22.0

v0.21.0

31 Oct 23:18
Compare
Choose a tag to compare

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

    (@timriley in #181)

  • Dry::System::Component#path has been removed and replaced by Component#require_path and Component#const_path (@timriley in #181)

  • Unused Dry::System::FileNotFoundError and Dry::System::InvalidComponentIdentifierTypeError errors have been removed (@timriley in #194)

Compare v0.20.0...v0.21.0

v0.20.0

12 Sep 12:41
Compare
Choose a tag to compare

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 in Container::Stubs (@mpokrywka in #178)

Changed

  • [internal] Upgraded to new setting API provided in dry-configurable 0.13.0 (@timriley in #179)

Compare v0.19.2...v0.20.0

v0.19.2

30 Aug 10:42
Compare
Choose a tag to compare

Changed

  • [internal] Improved compatibility with upcoming dry-configurable 0.13.0 release (@timriley in #186)

Compare v0.19.1...v0.19.2

v0.19.1

11 Jul 09:27
v0.19.1
e633504
Compare
Choose a tag to compare

Fixed

Compare v0.19.0...v0.19.1

v0.19.0

22 Apr 12:57
Compare
Choose a tag to compare

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 on Dry::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 a Dry::System::Component instance and returning a truthy or falsey value. Providing a proc allows an auto-registration policy to apply on a per-component basis
    • add_to_load_path, a boolean
    • default_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 default Dry::System::Loader to be used for the component dir
    • memoize, a boolean, to enable/disable memoizing all components in the directory, or a proc accepting a Dry::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 longer require 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 the auto_register and memoize component dir settings described above) now return a Dry::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 configured component_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 adding component_dirs instead (@timriley in #155)
  • [BREAKING] default_namespace container setting has been removed. Set it when adding component_dirs instead (@timriley in #155)
  • [BREAKING] loader container setting has been nested under component_dirs, now available as component_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, a Dry::Container::Error is raised in all cases of components failing to load (@timriley in #155)
  • [BREAKING] Dry::System::Container.auto_register! has been removed. Configure component_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 own loader (@timriley in #153)

Compare v0.18.1...v0.19.0