dry-effects 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +57 -5
- data/LICENSE +1 -1
- data/README.md +19 -11
- data/dry-effects.gemspec +33 -41
- data/lib/dry/effects/all.rb +4 -4
- data/lib/dry/effects/container.rb +1 -1
- data/lib/dry/effects/effect.rb +2 -2
- data/lib/dry/effects/effects/async.rb +3 -1
- data/lib/dry/effects/effects/cache.rb +13 -9
- data/lib/dry/effects/effects/cmp.rb +3 -1
- data/lib/dry/effects/effects/current_time.rb +4 -2
- data/lib/dry/effects/effects/defer.rb +3 -1
- data/lib/dry/effects/effects/env.rb +4 -2
- data/lib/dry/effects/effects/fork.rb +3 -1
- data/lib/dry/effects/effects/implicit.rb +4 -2
- data/lib/dry/effects/effects/interrupt.rb +4 -2
- data/lib/dry/effects/effects/lock.rb +8 -6
- data/lib/dry/effects/effects/parallel.rb +4 -2
- data/lib/dry/effects/effects/random.rb +5 -3
- data/lib/dry/effects/effects/reader.rb +1 -1
- data/lib/dry/effects/effects/resolve.rb +23 -3
- data/lib/dry/effects/effects/retry.rb +4 -2
- data/lib/dry/effects/effects/state.rb +4 -2
- data/lib/dry/effects/effects/timeout.rb +3 -1
- data/lib/dry/effects/effects/timestamp.rb +3 -1
- data/lib/dry/effects/errors.rb +4 -4
- data/lib/dry/effects/extensions/active_support/tagged_logging.rb +13 -0
- data/lib/dry/effects/extensions/auto_inject.rb +5 -5
- data/lib/dry/effects/extensions/system.rb +8 -7
- data/lib/dry/effects/extensions.rb +12 -4
- data/lib/dry/effects/frame.rb +30 -9
- data/lib/dry/effects/halt.rb +3 -3
- data/lib/dry/effects/handler.rb +1 -1
- data/lib/dry/effects/inflector.rb +1 -1
- data/lib/dry/effects/initializer.rb +17 -16
- data/lib/dry/effects/instruction.rb +1 -1
- data/lib/dry/effects/instructions/execute.rb +2 -1
- data/lib/dry/effects/instructions/raise.rb +2 -1
- data/lib/dry/effects/provider/class_interface.rb +2 -2
- data/lib/dry/effects/provider.rb +2 -2
- data/lib/dry/effects/providers/async.rb +2 -2
- data/lib/dry/effects/providers/cache.rb +2 -2
- data/lib/dry/effects/providers/cmp.rb +1 -1
- data/lib/dry/effects/providers/current_time/time_generators.rb +1 -1
- data/lib/dry/effects/providers/current_time.rb +5 -5
- data/lib/dry/effects/providers/defer.rb +6 -6
- data/lib/dry/effects/providers/env.rb +2 -2
- data/lib/dry/effects/providers/fork.rb +2 -2
- data/lib/dry/effects/providers/implicit.rb +1 -1
- data/lib/dry/effects/providers/interrupt.rb +3 -3
- data/lib/dry/effects/providers/lock.rb +6 -8
- data/lib/dry/effects/providers/parallel.rb +3 -3
- data/lib/dry/effects/providers/random.rb +74 -2
- data/lib/dry/effects/providers/reader.rb +1 -1
- data/lib/dry/effects/providers/resolve.rb +8 -7
- data/lib/dry/effects/providers/retry.rb +5 -7
- data/lib/dry/effects/providers/state.rb +2 -2
- data/lib/dry/effects/providers/timeout.rb +2 -2
- data/lib/dry/effects/providers/timestamp.rb +2 -2
- data/lib/dry/effects/stack.rb +6 -6
- data/lib/dry/effects/version.rb +1 -1
- data/lib/dry/effects.rb +7 -7
- metadata +22 -69
- data/.codeclimate.yml +0 -12
- data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
- data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -30
- data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
- data/.github/workflows/ci.yml +0 -74
- data/.github/workflows/docsite.yml +0 -34
- data/.github/workflows/sync_configs.yml +0 -34
- data/.gitignore +0 -12
- data/.rspec +0 -4
- data/.rubocop.yml +0 -95
- data/CODE_OF_CONDUCT.md +0 -13
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -23
- data/Rakefile +0 -8
- data/docsite/source/effects/cache.html.md +0 -84
- data/docsite/source/effects/current_time.html.md +0 -186
- data/docsite/source/effects/defer.html.md +0 -130
- data/docsite/source/effects/env.html.md +0 -144
- data/docsite/source/effects/interrupt.html.md +0 -109
- data/docsite/source/effects/parallel.html.md +0 -25
- data/docsite/source/effects/reader.html.md +0 -126
- data/docsite/source/effects/resolve.html.md +0 -188
- data/docsite/source/effects/state.html.md +0 -178
- data/docsite/source/effects/timeout.html.md +0 -42
- data/docsite/source/effects.html.md +0 -29
- data/docsite/source/index.html.md +0 -212
- data/examples/cmp.rb +0 -51
- data/examples/state.rb +0 -29
@@ -1,130 +0,0 @@
|
|
1
|
-
---
|
2
|
-
title: Deferred execution
|
3
|
-
layout: gem-single
|
4
|
-
name: dry-effects
|
5
|
-
---
|
6
|
-
|
7
|
-
`Defer` adds three methods for working with deferred code execution:
|
8
|
-
|
9
|
-
- `defer` accepts a block and executes it (potentially) on a thread pool. It returns an object that can be awaited with `wait`. These objects are `Promise`s made by `concurrent-ruby`. You can use their API, but it's not fully supported and tested in conjunction with effects.
|
10
|
-
- `wait` accepts a promise or an array of promises returned by `defer` and returns their values. The method blocks the current thread until all values are available.
|
11
|
-
- `later` postpones block execution until the handler is finished (see examples below).
|
12
|
-
|
13
|
-
### Defer
|
14
|
-
|
15
|
-
A simple example:
|
16
|
-
|
17
|
-
```ruby
|
18
|
-
class CreateUser
|
19
|
-
include Dry::Effects.Resolve(:user_repo, :send_invitation)
|
20
|
-
include Dry::Effects.Defer
|
21
|
-
|
22
|
-
def call(values)
|
23
|
-
user = user_repo.create(values)
|
24
|
-
defer { send_invitation.(user) }
|
25
|
-
user
|
26
|
-
end
|
27
|
-
end
|
28
|
-
```
|
29
|
-
|
30
|
-
In the code above, `send_invitation` is run on a thread pool. It's the simplest way to run code concurrently.
|
31
|
-
|
32
|
-
Code within the `defer` block can use some effects but not all of them. For instance, `Interrupt` is not supported because you cannot return from one thread to another. This is a limitation of Ruby and threads in general.
|
33
|
-
|
34
|
-
### Handling
|
35
|
-
|
36
|
-
The default handler uses `concurrent-ruby` to do the heavy lifting. As an option, it accepts the executor—usually a thread pool where the code will be run.
|
37
|
-
|
38
|
-
> Three special values are also supported: `:io` returns the global pool for long, blocking (IO) tasks, `:fast` returns the global pool for short, fast operations, and `:immediate` returns the global ImmediateExecutor object.
|
39
|
-
|
40
|
-
By default, `Dry::Effects::Handler.Defer` uses `:io`.
|
41
|
-
|
42
|
-
```ruby
|
43
|
-
class HandleDefer
|
44
|
-
include Dry::Effects::Handler.Defer(executor: :immediate)
|
45
|
-
|
46
|
-
def initialize(app)
|
47
|
-
@app = app
|
48
|
-
end
|
49
|
-
|
50
|
-
def call(env)
|
51
|
-
# defer tasks in @app will be run on the same thread
|
52
|
-
with_defer { @app.(env) }
|
53
|
-
end
|
54
|
-
end
|
55
|
-
```
|
56
|
-
|
57
|
-
The executor can be passed directly to `with_defer`:
|
58
|
-
|
59
|
-
```ruby
|
60
|
-
def call(env)
|
61
|
-
with_defer(executor: :fast) { @app.(env) }
|
62
|
-
end
|
63
|
-
```
|
64
|
-
|
65
|
-
### Using null executor
|
66
|
-
|
67
|
-
For skipping deferred tasks, create a mocked executor
|
68
|
-
|
69
|
-
```ruby
|
70
|
-
require 'concurrent/executor/executor_service'
|
71
|
-
|
72
|
-
NullExecutor = Object.new.extend(Concurrent::ExecutorService).tap do |null|
|
73
|
-
def null.post
|
74
|
-
end
|
75
|
-
end
|
76
|
-
```
|
77
|
-
|
78
|
-
and provide it in middleware
|
79
|
-
|
80
|
-
```ruby
|
81
|
-
class HandleDefer
|
82
|
-
include Dry::Effects::Handler.Defer
|
83
|
-
include Dry::Effects::Handler.Env(:environment)
|
84
|
-
|
85
|
-
def initialize(app)
|
86
|
-
@app = app
|
87
|
-
end
|
88
|
-
|
89
|
-
def call(env)
|
90
|
-
with_defer(executor: executor) { @app.(env) }
|
91
|
-
end
|
92
|
-
|
93
|
-
def executor
|
94
|
-
environment.equal?(:test) ? NullExecutor : :io
|
95
|
-
end
|
96
|
-
end
|
97
|
-
```
|
98
|
-
|
99
|
-
### Later
|
100
|
-
|
101
|
-
`later` doesn't return a result that can be awaited. Instead, it starts deferred blocks on handler exit. Consider this example:
|
102
|
-
|
103
|
-
```ruby
|
104
|
-
class CreateUser
|
105
|
-
def call(values)
|
106
|
-
user_repo.transaction do
|
107
|
-
user = user_repo.create(values)
|
108
|
-
defer { send_invitation.(user) }
|
109
|
-
user_account = account_repo.create(user)
|
110
|
-
user
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
```
|
115
|
-
|
116
|
-
There is no guarantee `send_invitation` will be run _after_ the transaction finishes. It may lead to race conditions or anomalies. If `account_repo.create` fails with an exception, the transaction will be rolled back yet the invitation will be sent!
|
117
|
-
|
118
|
-
`later` captures the block but doesn't run it:
|
119
|
-
|
120
|
-
```ruby
|
121
|
-
later { send_invitation.(user) }
|
122
|
-
```
|
123
|
-
|
124
|
-
The invitaition will be sent when `with_defer` exits:
|
125
|
-
|
126
|
-
```ruby
|
127
|
-
with_defer { @app.(env) }
|
128
|
-
```
|
129
|
-
|
130
|
-
It usually happens outside of any transaction so that anomalies don't occur.
|
@@ -1,144 +0,0 @@
|
|
1
|
-
---
|
2
|
-
title: Environment
|
3
|
-
layout: gem-single
|
4
|
-
name: dry-effects
|
5
|
-
---
|
6
|
-
|
7
|
-
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.
|
8
|
-
|
9
|
-
### Providing environment
|
10
|
-
|
11
|
-
```ruby
|
12
|
-
require 'dry/effects'
|
13
|
-
|
14
|
-
class EnvironmentMiddleware
|
15
|
-
include Dry::Effects::Handler.Env(environment: ENV['RACK_ENV'])
|
16
|
-
|
17
|
-
def initialize(app)
|
18
|
-
@app = app
|
19
|
-
end
|
20
|
-
|
21
|
-
def call(env)
|
22
|
-
with_env { @app.(env) }
|
23
|
-
end
|
24
|
-
end
|
25
|
-
```
|
26
|
-
|
27
|
-
### Using environment
|
28
|
-
|
29
|
-
By default, `Effects.Env` creates accessor to keys with the same name:
|
30
|
-
|
31
|
-
```ruby
|
32
|
-
class CreateUser
|
33
|
-
include Dry::Effects.Env(:environment)
|
34
|
-
|
35
|
-
def call(...)
|
36
|
-
#
|
37
|
-
log unless environemnt.eql?('test')
|
38
|
-
end
|
39
|
-
end
|
40
|
-
```
|
41
|
-
|
42
|
-
But you can pass a hash and use arbitrary method names:
|
43
|
-
|
44
|
-
```ruby
|
45
|
-
class CreateUser
|
46
|
-
include Dry::Effects.Env(env: :environment)
|
47
|
-
|
48
|
-
def call(...)
|
49
|
-
#
|
50
|
-
log unless env.eql?('test')
|
51
|
-
end
|
52
|
-
end
|
53
|
-
```
|
54
|
-
|
55
|
-
### Interaction with `ENV`
|
56
|
-
|
57
|
-
The Env handler will search for keys in `ENV` as a fallback:
|
58
|
-
|
59
|
-
```ruby
|
60
|
-
class SendRequest
|
61
|
-
include Dry::Effects.Env(endpoint: 'THIRD_PARTY')
|
62
|
-
|
63
|
-
def call(...)
|
64
|
-
# some code using `endpoint`
|
65
|
-
end
|
66
|
-
end
|
67
|
-
```
|
68
|
-
|
69
|
-
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:
|
70
|
-
|
71
|
-
```ruby
|
72
|
-
require 'dry/effects'
|
73
|
-
|
74
|
-
class SidekiqEnvMiddleware
|
75
|
-
include Dry::Effects::Handler.Env
|
76
|
-
|
77
|
-
def call(_worker, _job, _queue, &block)
|
78
|
-
with_env(&block)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
Sidekiq.configure_server do |config|
|
83
|
-
config.server_middleware do |chain|
|
84
|
-
chain.add SidekiqEnvMiddleware
|
85
|
-
end
|
86
|
-
end
|
87
|
-
```
|
88
|
-
|
89
|
-
In tests, you can pass `THIRD_PARTY` without modifying `ENV`:
|
90
|
-
|
91
|
-
```ruby
|
92
|
-
RSpec.describe SendRequest do
|
93
|
-
include Dry::Effects::Handler.Env
|
94
|
-
|
95
|
-
subject(:send_request) { described_class.new }
|
96
|
-
|
97
|
-
let(:endpoint) { "fake server" }
|
98
|
-
|
99
|
-
around { with_env('THIRD_PARTY' => endpoint, &ex) }
|
100
|
-
|
101
|
-
it 'sends a request to a fake server' do
|
102
|
-
send_request.(...)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
```
|
106
|
-
|
107
|
-
### Overriding handlers
|
108
|
-
|
109
|
-
By passing `overridable: true` you can override values provided by the nested handler:
|
110
|
-
|
111
|
-
```ruby
|
112
|
-
class Application
|
113
|
-
include Dry::Effects.Env(:foo)
|
114
|
-
|
115
|
-
def call
|
116
|
-
puts foo
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
class ProvidingContext
|
121
|
-
include Dry::Effects::Handler.Env
|
122
|
-
|
123
|
-
def call
|
124
|
-
with_env({ foo: 100 }, overridable: true) { yield }
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
class OverridingContext
|
129
|
-
include Dry::Effects::Handler.Env
|
130
|
-
|
131
|
-
def call
|
132
|
-
with_env(foo: 200) { yield }
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
overriding = OverridingContext.new
|
137
|
-
providing = ProvidingContext.new
|
138
|
-
app = Application.new
|
139
|
-
|
140
|
-
overriding.() { providing.() { app.() } }
|
141
|
-
# prints 200, coming from overriding context
|
142
|
-
```
|
143
|
-
|
144
|
-
Again, this is useful for testing, you pass `overridable: true` in the test environment and override environment values in specs.
|
@@ -1,109 +0,0 @@
|
|
1
|
-
---
|
2
|
-
title: Interrupt
|
3
|
-
layout: gem-single
|
4
|
-
name: dry-effects
|
5
|
-
---
|
6
|
-
|
7
|
-
Interrupt is an effect with the semantics of `raise`/`rescue` or `throw`/`catch`. It's added for consistency and compatibility with other effects. Underneath, it uses `raise` + `rescue` so that application code can detect the bubbling.
|
8
|
-
|
9
|
-
### Basic usage
|
10
|
-
|
11
|
-
If you know what exceptions are, this should look familiar:
|
12
|
-
|
13
|
-
```ruby
|
14
|
-
require 'dry/effects'
|
15
|
-
|
16
|
-
class RunDivision
|
17
|
-
include Dry::Effects::Handler.Interrupt(:division_by_zero, as: :catch_zero_division)
|
18
|
-
|
19
|
-
def call
|
20
|
-
error, answer = catch_zero_division do
|
21
|
-
yield
|
22
|
-
end
|
23
|
-
|
24
|
-
if error
|
25
|
-
:error
|
26
|
-
else
|
27
|
-
answer
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
class Divide
|
33
|
-
include Dry::Effects.Interrupt(:division_by_zero)
|
34
|
-
|
35
|
-
def call(dividend, divisor)
|
36
|
-
if divisor.zero?
|
37
|
-
division_by_zero
|
38
|
-
else
|
39
|
-
dividend / divisor
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
run = RunDivision.new
|
45
|
-
divide = Divide.new
|
46
|
-
|
47
|
-
app = -> a, b { run.() { divide.(a, b) } }
|
48
|
-
|
49
|
-
app.(10, 2) # => 5
|
50
|
-
app.(1, 0) # => :error
|
51
|
-
```
|
52
|
-
|
53
|
-
The handler returns a flag indicating whether there was an interruption. `false` means the block was run without interruption, `true` stands for the code was interrupted at some point.
|
54
|
-
|
55
|
-
### Payload
|
56
|
-
|
57
|
-
Interruption can have a payload:
|
58
|
-
|
59
|
-
```ruby
|
60
|
-
class Callee
|
61
|
-
include Dry::Effects.Interrupt(:halt)
|
62
|
-
|
63
|
-
def call
|
64
|
-
halt :foo
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
class Caller
|
69
|
-
include Dry::Effects::Handler.Interrupt(:halt, as: :catch_halt)
|
70
|
-
|
71
|
-
def call
|
72
|
-
_, result = catch_halt do
|
73
|
-
yield
|
74
|
-
:bar
|
75
|
-
end
|
76
|
-
|
77
|
-
result
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
caller = Caller.new
|
82
|
-
callee = Callee.new
|
83
|
-
|
84
|
-
caller.() { callee.() } # => :foo
|
85
|
-
caller.() { } # => :bar
|
86
|
-
```
|
87
|
-
|
88
|
-
### Composition
|
89
|
-
|
90
|
-
Every Interrupt effect has to have an identifier so that they don't overlap. It's an equivalent of exception types. You can nest handlers with different identifiers; they will work just as you would expect:
|
91
|
-
|
92
|
-
```ruby
|
93
|
-
class Catcher
|
94
|
-
include Dry::Effects::Handler(:div_error, as: :catch_div)
|
95
|
-
include Dry::Effects::Handler(:sqrt_error, as: :catch_sqrt)
|
96
|
-
|
97
|
-
def call
|
98
|
-
_, div_result = catch_div do
|
99
|
-
_, sqrt_result = catch_sqrt do
|
100
|
-
yield
|
101
|
-
end
|
102
|
-
|
103
|
-
sqrt_result
|
104
|
-
end
|
105
|
-
|
106
|
-
div_result
|
107
|
-
end
|
108
|
-
end
|
109
|
-
```
|
@@ -1,25 +0,0 @@
|
|
1
|
-
---
|
2
|
-
title: Parallel execution
|
3
|
-
layout: gem-single
|
4
|
-
name: dry-effects
|
5
|
-
---
|
6
|
-
|
7
|
-
There are two effects for using parallelism:
|
8
|
-
|
9
|
-
- `par` creates a unit of parallel execution;
|
10
|
-
- `join` combines an array of units to the array of their results.
|
11
|
-
|
12
|
-
`par`/`join` is almost identical to `defer`/`wait` from [`Defer`](/gems/dry-effects/0.1/effects/defer), the difference is in the semantics. `defer` is not supposed to be always awaited, it's usually fired-and-forgotten. On the contrary, `par` is meant to be `join`ed at some point.
|
13
|
-
|
14
|
-
```ruby
|
15
|
-
class MakeRequests
|
16
|
-
include Dry::Effects.Resolve(:make_request)
|
17
|
-
|
18
|
-
def call(urls)
|
19
|
-
# Run every request in parallel and combine their results
|
20
|
-
urls.map { |url| par { make_request.(url) } }.then { |pars| join(pars) }
|
21
|
-
end
|
22
|
-
end
|
23
|
-
```
|
24
|
-
|
25
|
-
Just as [`Defer`](/gems/dry-effects/0.1/effects/defer), `Parallel` uses `concurrent-ruby` under the hood.
|
@@ -1,126 +0,0 @@
|
|
1
|
-
---
|
2
|
-
title: Reader
|
3
|
-
layout: gem-single
|
4
|
-
name: dry-effects
|
5
|
-
---
|
6
|
-
|
7
|
-
Reader is the simplest effect. It passes a value down to the stack.
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
require 'dry/effects'
|
11
|
-
|
12
|
-
class SetLocaleMiddleware
|
13
|
-
include Dry::Effects::Handler.Reader(:locale)
|
14
|
-
|
15
|
-
def initialize(app)
|
16
|
-
@app = app
|
17
|
-
end
|
18
|
-
|
19
|
-
def call(env)
|
20
|
-
with_locale(detect_locale(env)) do
|
21
|
-
@app.(env)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def detect_locale(env)
|
26
|
-
# arbitrary detection logic
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
### Anywhere in the app
|
31
|
-
|
32
|
-
class GreetUser
|
33
|
-
include Dry::Effects.Reader(:locale)
|
34
|
-
|
35
|
-
def call(user)
|
36
|
-
case locale
|
37
|
-
when :en then "Hello #{user.name}"
|
38
|
-
when :de then "Hallo #{user.name}"
|
39
|
-
when :ru then "Привет, #{user.name}"
|
40
|
-
when :it then "Ciao #{user.name}"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
```
|
45
|
-
|
46
|
-
### Testing with Reader
|
47
|
-
|
48
|
-
If you run `GreetUser#call` without a Reader handler, it will raise an error. For unit tests you'll need some wrapping code:
|
49
|
-
|
50
|
-
```ruby
|
51
|
-
RSpec.describe GreetUser do
|
52
|
-
include Dry::Effects::Handler.Reader(:locale)
|
53
|
-
|
54
|
-
subject(:greet) { described_class.new }
|
55
|
-
|
56
|
-
let(:user) { double(:user, name: 'John') }
|
57
|
-
|
58
|
-
it 'uses the current locale to greet the user' do
|
59
|
-
examples = {
|
60
|
-
en: 'Hello John',
|
61
|
-
de: 'Hallo John',
|
62
|
-
ru: 'Привет, John',
|
63
|
-
it: 'Ciao John'
|
64
|
-
}
|
65
|
-
|
66
|
-
examples.each do |locale, expected_greeting|
|
67
|
-
with_locale(locale) do
|
68
|
-
expect(greet.(user)).to eql(expected_greeting)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
```
|
74
|
-
|
75
|
-
You can provide locale in an `around(:each)` hook:
|
76
|
-
|
77
|
-
```ruby
|
78
|
-
require 'dry/effects'
|
79
|
-
|
80
|
-
# Build a provider object with .call interface
|
81
|
-
locale_provider = Object.new.extend(Dry::Effects::Handler.Reader(:locale, as: :call))
|
82
|
-
|
83
|
-
RSpec.configure do |config|
|
84
|
-
config.around(:each) do |ex|
|
85
|
-
locale_provider.(:en, &ex)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
```
|
89
|
-
|
90
|
-
### Nesting readers
|
91
|
-
|
92
|
-
As a general rule, if there are two handlers in the stack, the nested takes precedence:
|
93
|
-
|
94
|
-
```ruby
|
95
|
-
require 'dry/effects'
|
96
|
-
|
97
|
-
extend Dry::Effects::Handler.Reader(:locale)
|
98
|
-
extend Dry::Effects.Reader(:locale)
|
99
|
-
|
100
|
-
with_locale(:en) { with_locale(:de) { locale } } # => :de
|
101
|
-
```
|
102
|
-
|
103
|
-
### Mixing readers
|
104
|
-
|
105
|
-
Every Reader has an identifier. Handlers with different identifiers won't interfere:
|
106
|
-
|
107
|
-
```ruby
|
108
|
-
require 'dry/effects'
|
109
|
-
|
110
|
-
extend Dry::Effects::Handler.Reader(:locale)
|
111
|
-
extend Dry::Effects::Handler.Reader(:context)
|
112
|
-
extend Dry::Effects.Reader(:locale)
|
113
|
-
extend Dry::Effects.Reader(:context)
|
114
|
-
|
115
|
-
with_locale(:en) { with_context(:background) { [locale, context] } } # => [:en, :background]
|
116
|
-
# Order doesn't matter:
|
117
|
-
with_context(:background) { with_locale(:en) { [locale, context] } } # => [:en, :background]
|
118
|
-
```
|
119
|
-
|
120
|
-
### Relation to State
|
121
|
-
|
122
|
-
Reader is part of the [State](/gems/dry-effects/0.1/effects/state) effect.
|
123
|
-
|
124
|
-
### Tradeoffs of implicit passing
|
125
|
-
|
126
|
-
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.).
|