Resolve (Dependency Injection)
Resolve is an effect for injecting dependencies. A simple usage example:
require 'dry/effects'
class CreateUser
  include Dry::Effects.Resolve(:user_repo)
  def call(values)
    name = values.values_at(:first_name, :last_name).join(' ')
    user_repo.create(**values.merge(name: name))
  end
end
Providing user_repo in tests:
RSpec.describe CreateUser do
  # adds #provide
  include Dry::Effects::Handler.Resolve
  subject(:create_user) { described_class.new }
  let(:user_repo) { double(:user_repo) }
  it 'creates a user' do
    expect(user_repo).to receive(:create).with(
      first_name: 'John',
      last_name: 'Doe',
      name: 'John Doe'
    )
    provide(user_repo: user_repo) { create_user.(first_name: 'John', last_name: 'Doe') }
  end
end
Providing dependencies with middleware:
class ProviderMiddleware
  include Dry::Effects::Handler.Resolve
  def initialize(app, dependencies)
    @app = app
    @dependencies = dependencies
  end
  def call(env)
    provide(@dependencies) { @app.(env) }
  end
end
Then in config.ru:
# ...some bootstrapping code ...
use ProviderMiddleware, user_repo: UserRepo.new
run Application.new
            
Compatibility with dry-container and dry-system
Any object that responds to .key? and .[] can be used for providing dependencies. Thus, the default Resolve provider is compatible with dry-container and dry-system out of the box.
def call(env)
  # Assuming App is a subclass of Dry::System::Container
  provide(App) { @app.(env) }
end
Providing static values
One can pass a container to the module builder:
class ProviderMiddleware
  include Dry::Effects::Handler.Resolve(Application)
  def initialize(app)
    @app = app
  end
  def call(env)
    # Here Application will be used for resolving dependencies
    provide { @app.(env) }
  end
end
Injecting many keys and using aliases
require 'dry/effects'
class CreateUser
  include Dry::Effects.Resolve(
    # Injected as .schema
    # but resolved with 'operations.create_user.schema'
    'operations.create_user.schema',
    # Injected as .repo
    # but resolved with 'repos.user_repo'
    repo: 'repos.user_repo'
  )
  def call(values)
    result = schema.(values)
    if result.success?
      user = repo.create(result.to_h)
      [:ok, user]
    else
      [:err, result]
    end
  end
end
Overriding dependencies in test environment
Sometimes you may want to push dependencies through an existing handler. This is normally needed for testing when you want to replace some dependencies in a test environment for an assembled app, like a Rack application. Passing overridable: true enables it:
require 'dry/effects'
class ProviderMiddleware
  include Dry::Effects::Handler.Resolve
  def initialize(app)
    @app = app
  end
  def call(env)
    provide(Application, overridable: overridable?) { @app.(env) }
  end
  def overridable?
    ENV['RACK_ENV'].eql?('test')
  end
end
Now in tests, you can override some dependencies at will:
require 'dry/effects'
require 'rack/test'
RSpec.describe do
  include Rack::Test::Methods
  include Dry::Effects::Handler.Provider
  let(:app) do
    # building an assembled rack app
  end
  describe 'POST /users' do
    let(:user_repo) { double(:user_repo) }
    it 'creates a user' do
      expect(user_repo).to receive(:create).with(
        first_name: 'John', last_name: 'Doe'
      ).and_return(1)
      # Overriding one dependency
      # It will only work if `overridable: true` is passed
      # in the middleware
      provide('repos.user_repo' => user_repo) do
        post(
          '/users',
          JSON.dump(first_name: 'John', last_name: 'Doe'),
          'CONTENT_TYPE' => 'application/json'
        )
      end
    end
  end
end