Environment
Configuring code via ENV can be handy, but testing it by mutating a global constant is usually not. Env is similar to Reader but exists for passing simple key-value pairs, precisely what ENV does.
Providing environment
require 'dry/effects'
class EnvironmentMiddleware
  include Dry::Effects::Handler.Env(environment: ENV['RACK_ENV'])
  def initialize(app)
    @app = app
  end
  def call(env)
    with_env { @app.(env) }
  end
end
Using environment
By default, Effects.Env creates accessor to keys with the same name:
class CreateUser
  include Dry::Effects.Env(:environment)
  def call(...)
    #
    log unless environemnt.eql?('test')
  end
end
But you can pass a hash and use arbitrary method names:
class CreateUser
  include Dry::Effects.Env(env: :environment)
  def call(...)
    #
    log unless env.eql?('test')
  end
end
            
Interaction with ENV
The Env handler will search for keys in ENV as a fallback:
class SendRequest
  include Dry::Effects.Env(endpoint: 'THIRD_PARTY')
  def call(...)
    # some code using `endpoint`
  end
end
In a production environment, it would be enough to pass THIRD_PARTY an environment variable and call with_env at the top of the application:
require 'dry/effects'
class SidekiqEnvMiddleware
  include Dry::Effects::Handler.Env
  def call(_worker, _job, _queue, &block)
    with_env(&block)
  end
end
Sidekiq.configure_server do |config|
  config.server_middleware do |chain|
    chain.add SidekiqEnvMiddleware
  end
end
In tests, you can pass THIRD_PARTY without modifying ENV:
RSpec.describe SendRequest do
  include Dry::Effects::Handler.Env
  subject(:send_request) { described_class.new }
  let(:endpoint) { "fake server" }
  around { with_env('THIRD_PARTY' => endpoint, &ex) }
  it 'sends a request to a fake server' do
    send_request.(...)
  end
end
Overriding handlers
By passing overridable: true you can override values provided by the nested handler:
class Application
  include Dry::Effects.Env(:foo)
  def call
    puts foo
  end
end
class ProvidingContext
  include Dry::Effects::Handler.Env
  def call
    with_env({ foo: 100 }, overridable: true) { yield }
  end
end
class OverridingContext
  include Dry::Effects::Handler.Env
  def call
    with_env(foo: 200) { yield }
  end
end
overriding = OverridingContext.new
providing = ProvidingContext.new
app = Application.new
overriding.() { providing.() { app.() } }
# prints 200, coming from overriding context
Again, this is useful for testing, you pass overridable: true in the test environment and override environment values in specs.