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.

2 Likes

Hello, I am so sorry about the very late reply. I am working on the project in my spare time, so things are going very slowly :slight_smile:

Anyway, your reply was really helpful, thanks for pointing out the Hanami as an example. Actually, I was able to find two very informative posts that were posted on the HanamiMastery blog:

  1. Dependency Injection in Ruby from 0 to hero (Part 1)

  2. Dependency Injection in Ruby - GOD Level! Meet dry-system! (Part 2)

These posts were very helpful, and now I have a better understanding of how to use the dry-system gem. I had to rewrite my code “from scratch”, but at the end of the day It was worth it :slight_smile:

Thank you for your assistance!

2 Likes

Another excellent series of blog posts is Ryan Bigg’s Dry-rb showcase

2 Likes