Reader is the simplest effect. It passes a value down to the stack.
include Dry::Effects::Handler.Reader(:locale)
@app = app
end
with_locale(detect_locale(env)) do
@app.(env)
end
end
# arbitrary detection logic
end
end
### Anywhere in the app
include Dry::Effects.Reader(:locale)
case locale
when :en then "Hello "
when :de then "Hallo "
when :ru then "Привет, "
when :it then "Ciao "
end
end
end
Testing with Reader
If you run GreetUser#call
without a Reader handler, it will raise an error. For unit tests you'll need some wrapping code:
RSpec.describe GreetUser do
include Dry::Effects::Handler.Reader(:locale)
subject(:greet) { described_class.new }
let(:user) { double(:user, name: 'John') }
it 'uses the current locale to greet the user' do
examples = {
en: 'Hello John',
de: 'Hallo John',
ru: 'Привет, John',
it: 'Ciao John'
}
examples.each do
with_locale(locale) do
expect(greet.(user)).to eql(expected_greeting)
end
end
end
end
You can provide locale in an around(:each)
hook:
# Build a provider object with .call interface
locale_provider = Object.new.extend(Dry::Effects::Handler.Reader(:locale, as: :call))
RSpec.configure do
config.around(:each) do
locale_provider.(:en, &ex)
end
end
Nesting readers
As a general rule, if there are two handlers in the stack, the nested takes precedence:
extend Dry::Effects::Handler.Reader(:locale)
extend Dry::Effects.Reader(:locale)
with_locale(:en) { with_locale(:de) { locale } } # => :de
Mixing readers
Every Reader has an identifier. Handlers with different identifiers won't interfere:
extend Dry::Effects::Handler.Reader(:locale)
extend Dry::Effects::Handler.Reader(:context)
extend Dry::Effects.Reader(:locale)
extend Dry::Effects.Reader(:context)
with_locale(:en) { with_context(:background) { [locale, context] } } # => [:en, :background]
# Order doesn't matter:
with_context(:background) { with_locale(:en) { [locale, context] } } # => [:en, :background]
Relation to State
Reader is part of the State effect.
Tradeoffs of implicit passing
Passing values implicitly is not good or bad by itself; you should consider how it affects your code in every case. Providing the current locale is a good example where reader effect can be justified. On the other hand, passing optional values such as the IP-address of the current user should be done explicitly because they are not always present (consider background jobs, rake tasks, etc.).