dry-effects

v0.5
  1. Introduction
  2. Effects
    1. Cache
    2. Current Time
    3. Deferred execution
    4. Environment
    5. Interrupt
    6. Parallel execution
    7. Reader
    8. Resolve (Dependency Injection)
    9. State
    10. Timeout

TOC

  1. Providing environment
  2. Using environment
  3. Interaction with ENV
  4. Overriding handlers

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.