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.