Questions about the dry-system container settings

Hello,

I have a hard time understanding how I can change container settings before the container finalization.

Let me illustrate my point with a short single file code snippet:

# frozen_string_literal: true

require "dry/system"

class Container < Dry::System::Container
  require "dry/system/provider_sources"

  configure do |config|
    use :env

    config.root = "./"

    register_provider(:settings, from: :dry_system) do
      settings do
        setting :name, default: "Ruby"
      end
    end

    register :my_component do
      MyComponent.new
    end
  end
end

Import = Container.injector

class MyComponent
  include Import[:settings]

  def say_hi
    puts "Hello, #{settings.name}"
  end
end

# I want to change the Container[:settings].name setting here.

Container.finalize!
Container["my_component"].say_hi

So here we have a setting Container[:settings].name with a default value Ruby, and the component MyComponent that uses that setting. Now, what should I do in order to change the setting’s value before the container finalization? I have tried to re-configure container, but got the Cannot modify frozen config (Dry::Configurable::FrozenConfigError) error.

The other minor problem is that my code fails without these two lines:
use :env
and
require "dry/system/provider_sources"

The documentation page does not have these, so I am a bit confused. Is this a problem with my environment or it is a lack of details in the documentation?

Any help would be highly appreciated.

Part of the problem is that you’re doing too much inside the configure block, that is for container configuration not your application per se.

My first suggestion is to split this out into separate files as intended:

system/application/container.rb

require "dry/system"

class Application < Dry::System::Container
  configure do |config|
    config.root = Pathname(File.expand_path("../..", __dir__))
    config.component_dirs.add "app"
 end

  use :env, inferrer: -> { ENV.fetch("APP_ENV", "development") }
end

Import = Application.injector

system/providers/settings.rb

require "dry/system/provider_sources"

Application.register_provider :settings, from: :dry_system do
  settings do
    setting :name, default: "Ruby"
  end
end

app/my_component.rb

class MyComponent
  include Import[:settings]

  def say_hi
    puts "Hello, #{settings.name}"
  end
end
$ ruby -Isystem -rapplication/container -e 'puts Application[:my_component].say_hi'
Hello, Ruby

$ NAME=World ruby -Isystem -rapplication/container -e 'puts Application[:my_component].say_hi'
Hello, World

The standard way for the production environment to inject settings is through env vars. In development, you can define a .env file

$ echo "NAME=World" > .env
$ ruby -Isystem -rapplication/container -e 'puts Application[:my_component].say_hi'
Hello, World

The require 'dry/system/provider_dirs' is an annoyance, as documented in this GitHub Issue.

Getting started is somewhat difficult because dry-system is really more of a meta-framework. I would suggest taking a look at Hanami 2 as a batteries-included implementation of it.

1 Like