waterfall 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -0
- data/Gemfile.lock +4 -1
- data/README.md +105 -215
- data/Rakefile +1 -0
- data/changelog.md +6 -0
- data/lib/waterfall/predicates/base.rb +0 -11
- data/lib/waterfall/predicates/chain.rb +10 -4
- data/lib/waterfall/predicates/when_falsy.rb +5 -14
- data/lib/waterfall/predicates/when_truthy.rb +5 -1
- data/lib/waterfall/version.rb +1 -1
- data/spec/integration_spec.rb +44 -18
- metadata +18 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 926586c0692a21fc3e8110a28818a907c8b10d2d
|
4
|
+
data.tar.gz: 438651277ce425c2a9667e003a9f4deb56bfd61a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c4c1bb9dc927cd752e7b184274373d624ad625d5d316e0930238c96a75c3fa0b5ee9129f4ef3865271d490f5e1d86d0eadd60e553f6a06e3c9636a15cbba5fa6
|
7
|
+
data.tar.gz: f10b48892968cb75add57c80b13374413820902a12324c97f3b531a6c1f96c4d832762732c3b61d6e3116fa0e247f3830b3e93edf10f762330182a78a7de58eb
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -6,95 +6,78 @@
|
|
6
6
|
|
7
7
|
Be able to chain ruby commands, and treat them like a flow.
|
8
8
|
|
9
|
-
|
9
|
+
It provides a new approach to flow control.
|
10
10
|
|
11
|
-
|
11
|
+
When logic is complicated, waterfalls show their true power and let you write intention revealing code. Above all they excel at chaining services.
|
12
12
|
|
13
|
+
General presentation slides can [be found here](https://slides.com/apneadiving/code-ruby-like-you-build-lego).
|
13
14
|
|
14
|
-
####
|
15
|
+
#### Overview
|
15
16
|
|
16
|
-
|
17
|
-
Wf.new
|
18
|
-
.when_falsy { @user.update(user_params) }
|
19
|
-
.dam { @user.errors }
|
20
|
-
.chain { render json: @user }
|
21
|
-
.on_dam { |errors| render json: { errors: errors.full_messages }, status: 422 }
|
22
|
-
```
|
17
|
+
A waterfall object has its own flow of commands, you can chain your commands and if something wrong happens, you dam the flow which bypasses the rest of the commands.
|
23
18
|
|
24
|
-
|
19
|
+
Here is a basic representation:
|
20
|
+
- green, the flow goes on, `chain` by `chain`
|
21
|
+
- red its bypassed and only `on_dam` blocks are executed.
|
25
22
|
|
26
|
-
|
27
|
-
Coding is all about writing a flow of commands.
|
23
|
+
![Waterfall Logo](http://apneadiving.github.io/images/waterfall_principle.png)
|
28
24
|
|
29
|
-
|
25
|
+
#### Example
|
30
26
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
* what should a good service return?
|
35
|
-
* how to handle errors?
|
36
|
-
* how to call a service within a service?
|
37
|
-
* how to chain services / commands
|
38
|
-
|
39
|
-
Those topics are discussed in [the slides here](https://slides.com/apneadiving/service-objects-waterfall-rails).
|
27
|
+
```ruby
|
28
|
+
class FetchUser
|
29
|
+
include Waterfall
|
40
30
|
|
31
|
+
def initialize(user_id)
|
32
|
+
@user_id = user_id
|
33
|
+
end
|
41
34
|
|
42
|
-
|
35
|
+
def call
|
36
|
+
chain { @response = HTTParty.get("https://jsonplaceholder.typicode.com/users/#{@user_id}") }
|
37
|
+
when_falsy { @response.success? }
|
38
|
+
.dam { "Error status #{@response.code}" }
|
39
|
+
chain(:user) { @response.body }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
43
|
|
44
|
-
|
44
|
+
and call / chain:
|
45
45
|
|
46
|
-
|
46
|
+
```ruby
|
47
|
+
Wf.new
|
48
|
+
.chain(user1: :user) { FetchUser.new(1) }
|
49
|
+
.chain(user2: :user) { FetchUser.new(2) }
|
50
|
+
.chain {|outflow| puts(outflow.user1, outflow.user2) } # report success
|
51
|
+
.on_dam {|error| puts(error) } # report error
|
52
|
+
```
|
47
53
|
|
48
|
-
|
54
|
+
Which works like:
|
49
55
|
|
50
|
-
|
56
|
+
![Waterfall Logo](http://apneadiving.github.io/images/waterfall_full_example.png)
|
51
57
|
|
52
58
|
## Installation
|
53
59
|
|
54
|
-
|
55
|
-
|
56
|
-
For installation:
|
60
|
+
For installation, in your gemfile:
|
57
61
|
|
58
|
-
gem 'waterfall'
|
62
|
+
gem 'waterfall'
|
59
63
|
|
64
|
+
then `bundle` as usual.
|
60
65
|
|
61
66
|
## Waterfall mixin
|
62
67
|
|
63
|
-
### Overview
|
64
|
-
|
65
|
-
The following are equivalent:
|
66
|
-
```ruby
|
67
|
-
# 1
|
68
|
-
Wf.new.chain{ 1 + 1 }
|
69
|
-
|
70
|
-
# 2
|
71
|
-
class MyService
|
72
|
-
include Waterfall
|
73
|
-
|
74
|
-
def call
|
75
|
-
self.chain{ 1 + 1 }
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
MyService.new.call
|
80
|
-
```
|
81
|
-
|
82
|
-
This illustrates one convention classes including the mixin should obey: respond to `call`
|
83
|
-
|
84
68
|
### Outputs
|
85
69
|
|
86
70
|
Each waterfall has its own `outflow` and `error_pool`.
|
87
71
|
|
88
72
|
`outflow` is an Openstruct so you can get/set its property like a hash or like a standard object.
|
89
73
|
|
90
|
-
|
74
|
+
### Wiki
|
75
|
+
Wiki contains many details, please check appropriate pages:
|
91
76
|
|
92
|
-
|
77
|
+
- [Predicates](https://github.com/apneadiving/waterfall/wiki/Predicates)
|
78
|
+
- [Wf Object](https://github.com/apneadiving/waterfall/wiki/Wf-object)
|
79
|
+
- [Testing](https://github.com/apneadiving/waterfall/wiki/Testing)
|
93
80
|
|
94
|
-
* have a standard way to deal with errors
|
95
|
-
* can deal with multiple errors
|
96
|
-
* support I18n out of the box
|
97
|
-
* can use your model errors out of the box
|
98
81
|
|
99
82
|
## Illustration of chaining
|
100
83
|
Doing
|
@@ -121,80 +104,6 @@ is the same as doing:
|
|
121
104
|
|
122
105
|
Hopefully you better get the chaining power this way.
|
123
106
|
|
124
|
-
## Predicates
|
125
|
-
|
126
|
-
### chain(name_or_mapping = nil, &block) | block signature: (outflow, waterfall)
|
127
|
-
|
128
|
-
Chain is the main predicate, what it does depends on what the block returns
|
129
|
-
```ruby
|
130
|
-
# main waterfall
|
131
|
-
Wf.new
|
132
|
-
.chain(foo: :bar) do
|
133
|
-
# child waterfall
|
134
|
-
Wf.new.chain(:bar){ 1 }.chain(:baz){ 2 }.chain{ 3 }
|
135
|
-
end
|
136
|
-
```
|
137
|
-
##### when block doesn't return a waterfall
|
138
|
-
|
139
|
-
The child waterfall would have the following outflow: `{ bar: 1, baz: 2 }`
|
140
|
-
|
141
|
-
This illustrates that when the block returns a value which is not a waterfall, it stores the returned value of the block inside the `name_or_mapping` key of the `outflow` or doesn't store it if `name_or_mapping` is `nil`.
|
142
|
-
|
143
|
-
Be aware those are equivalent:
|
144
|
-
|
145
|
-
```ruby
|
146
|
-
Wf.new.chain(:foo) { 1 }
|
147
|
-
Wf.new.chain{|outflow| outflow[:foo] = 1 }
|
148
|
-
Wf.new.chain{|outflow| outflow.foo = 1 }
|
149
|
-
Wf.new.chain{|outflow, waterfall| waterfall.update_outflow(:foo, 1) }
|
150
|
-
Wf.new.chain{|outflow, waterfall| waterfall.outflow.foo = 1 }
|
151
|
-
```
|
152
|
-
##### when block returns a waterfall
|
153
|
-
|
154
|
-
The main waterfall would have the following outflow: `{ foo: 1 }`
|
155
|
-
|
156
|
-
The main waterfall above receives the child waterfall as a return value of its `chain` block.
|
157
|
-
All waterfalls have independent outflows.
|
158
|
-
|
159
|
-
If `name_or_mapping` is `nil`, the main waterfall's `outflow` wouldnt be affected by its child (but if the child is dammed, the parent will be dammed).
|
160
|
-
|
161
|
-
If `name_or_mapping` is a `hash`, the format must be read as `{ name_in_parent_waterfall: :name_from_child_waterfall}`. In the above example, the child returned an `outflow` with a `bar` key which has be renamed as `foo` in the main one.
|
162
|
-
|
163
|
-
It may look useless, because most of the time you may not rename, but... It makes things clear. You know exactly what you expect and you know exactly that you dont expect the rest the child may provide.
|
164
|
-
|
165
|
-
### when_falsy(&block) | block signature: (error_pool, waterfall)
|
166
|
-
|
167
|
-
This predicate must ***always*** be used followed with `dam` like:
|
168
|
-
|
169
|
-
```ruby
|
170
|
-
Wf.new
|
171
|
-
.chain(:foo) { 1 }
|
172
|
-
.when_falsy { true }
|
173
|
-
.dam { "this wouldnt be executed" }
|
174
|
-
.when_falsy { false }
|
175
|
-
.dam { "errrrr" }
|
176
|
-
.chain(:bar) { 2 }
|
177
|
-
.on_dam {|error_pool| puts error_pool }
|
178
|
-
```
|
179
|
-
|
180
|
-
If the block returns a falsy value, it executes the `dam` block, which will store the returned value in the `error_pool`.
|
181
|
-
|
182
|
-
Once the waterfall is dammed, all following `chain` blocks are skipped (wont be executed). And all the following `on_dam` block would be executed.
|
183
|
-
|
184
|
-
As a result the example above would return a waterfall object having its `outflow` equal to `{ foo: 1 }`. Remember: it has been dammed before `bar` would have been set.
|
185
|
-
|
186
|
-
Its `error_pool` would be `"errrrr"` and it would be `puts` as a result of the `on_dam`
|
187
|
-
|
188
|
-
Be aware those are equivalent:
|
189
|
-
|
190
|
-
```ruby
|
191
|
-
Wf.new.when_falsy{ false }.dam{ 'errrr' }
|
192
|
-
Wf.new.chain{ |outflow, waterfall| waterfall.dam('errrr') unless false }
|
193
|
-
```
|
194
|
-
|
195
|
-
### when_truthy(&block) | block signature: (error_pool, waterfall)
|
196
|
-
|
197
|
-
Behaves the same as `when_falsy` except it dams when its return value is truthy
|
198
107
|
|
199
108
|
## Syntactic sugar
|
200
109
|
Given:
|
@@ -219,85 +128,7 @@ Wf.new
|
|
219
128
|
```
|
220
129
|
Both are the same: if a block returns a waterfall which was not executed, it will execute it (hence the `call` convention)
|
221
130
|
|
222
|
-
### on_dam(&block) | block signature: (error_pool, outflow, waterfall)
|
223
|
-
|
224
|
-
Its block is executed whenever the waterfall is dammed, skipped otherwise.
|
225
|
-
|
226
|
-
```ruby
|
227
|
-
Wf.new
|
228
|
-
.when_falsy { false }
|
229
|
-
.on_dam {|error_pool, outflow, waterfall| puts error_pool }
|
230
|
-
```
|
231
|
-
|
232
|
-
## Error propagation
|
233
131
|
|
234
|
-
Whenever a a waterfall is dammed, all the following chains are skipped.
|
235
|
-
|
236
|
-
* all the following chains are skipped
|
237
|
-
* all `on_dam` blocks are executed
|
238
|
-
|
239
|
-
## Testing a Waterfall service
|
240
|
-
|
241
|
-
Say I have this service:
|
242
|
-
```ruby
|
243
|
-
class AuthenticateUser
|
244
|
-
include Waterfall
|
245
|
-
include ActiveModel::Validations
|
246
|
-
|
247
|
-
validates :user, presence: true
|
248
|
-
attr_reader :user
|
249
|
-
|
250
|
-
def initialize(email, password)
|
251
|
-
@email, @password = email, @password
|
252
|
-
end
|
253
|
-
|
254
|
-
def call
|
255
|
-
self
|
256
|
-
.chain { @user = User.authenticate(@email, @password) }
|
257
|
-
.when_falsy { valid? }
|
258
|
-
.dam { errors }
|
259
|
-
.chain(:user) { user }
|
260
|
-
end
|
261
|
-
end
|
262
|
-
```
|
263
|
-
I could spec it this way:
|
264
|
-
```ruby
|
265
|
-
describe AuthenticateUser do
|
266
|
-
let(:email) { 'email@email.com' }
|
267
|
-
let(:password) { 'password' }
|
268
|
-
subject(:service) { AuthenticateUser.new(email, password).call }
|
269
|
-
|
270
|
-
context "when given valid credentials" do
|
271
|
-
let(:user) { double(:user) }
|
272
|
-
|
273
|
-
before do
|
274
|
-
allow(User).to receive(:authenticate).with(email, password).and_return(user)
|
275
|
-
end
|
276
|
-
|
277
|
-
it "succeeds" do
|
278
|
-
expect(service.dammed?).to be false
|
279
|
-
end
|
280
|
-
|
281
|
-
it "provides the user" do
|
282
|
-
expect(service.outflow.user).to eq(user)
|
283
|
-
end
|
284
|
-
end
|
285
|
-
|
286
|
-
context "when given invalid credentials" do
|
287
|
-
before do
|
288
|
-
allow(User).to receive(:authenticate).with(email, password).and_return(nil)
|
289
|
-
end
|
290
|
-
|
291
|
-
it "fails" do
|
292
|
-
expect(service.dammed?).to be true
|
293
|
-
end
|
294
|
-
|
295
|
-
it "provides a failure message" do
|
296
|
-
expect(service.error_pool).to be_present
|
297
|
-
end
|
298
|
-
end
|
299
|
-
end
|
300
|
-
```
|
301
132
|
Syntax advice
|
302
133
|
=========
|
303
134
|
```ruby
|
@@ -323,6 +154,16 @@ self
|
|
323
154
|
|
324
155
|
Tips
|
325
156
|
=========
|
157
|
+
### Error pool
|
158
|
+
For the error_pool, its up to you. But using Rails, I usually include ActiveModel::Validations in my services.
|
159
|
+
|
160
|
+
Thus you:
|
161
|
+
|
162
|
+
- have a standard way to deal with errors
|
163
|
+
- can deal with multiple errors
|
164
|
+
- support I18n out of the box
|
165
|
+
- can use your model errors out of the box
|
166
|
+
|
326
167
|
### Conditional Flow
|
327
168
|
In a service, there is one and single flow, so if you need conditionals to branch off, you can do:
|
328
169
|
```ruby
|
@@ -335,11 +176,60 @@ else
|
|
335
176
|
end
|
336
177
|
```
|
337
178
|
|
179
|
+
### Rails and transactions
|
180
|
+
I'm used to wrap every single object involving database interactions within transactions, so it can be rolled back on error.
|
181
|
+
Here is my usual setup:
|
182
|
+
```ruby
|
183
|
+
module Waterfall
|
184
|
+
extend ActiveSupport::Concern
|
338
185
|
|
339
|
-
|
340
|
-
|
341
|
-
|
186
|
+
class Rollback < StandardError; end
|
187
|
+
|
188
|
+
def with_transaction(&block)
|
189
|
+
ActiveRecord::Base.transaction(requires_new: true) do
|
190
|
+
yield
|
191
|
+
on_dam do
|
192
|
+
raise Waterfall::Rollback
|
193
|
+
end
|
194
|
+
end
|
195
|
+
rescue Waterfall::Rollback
|
196
|
+
self
|
197
|
+
end
|
198
|
+
end
|
199
|
+
```
|
200
|
+
|
201
|
+
And to use it:
|
202
|
+
```ruby
|
203
|
+
class AuthenticateUser
|
204
|
+
include Waterfall
|
205
|
+
include ActiveModel::Validations
|
206
|
+
|
207
|
+
validates :user, presence: true
|
208
|
+
attr_reader :user
|
209
|
+
|
210
|
+
def initialize(email, password)
|
211
|
+
@email, @password = email, @password
|
212
|
+
end
|
213
|
+
|
214
|
+
def call
|
215
|
+
with_transaction do
|
216
|
+
chain { @user = User.authenticate(@email, @password) }
|
217
|
+
when_falsy { valid? }
|
218
|
+
.dam { errors }
|
219
|
+
chain(:user) { user }
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
```
|
224
|
+
The huge benefit is that if you call services from services, everything will be rolled back.
|
225
|
+
|
226
|
+
Examples / Presentations
|
227
|
+
========================
|
228
|
+
- Check the [wiki for other examples](https://github.com/apneadiving/waterfall/wiki/Refactoring-examples).
|
229
|
+
- [Structure and chain your POROs](http://slides.com/apneadiving/structure-and-chain-your-poros).
|
230
|
+
- [Service objects implementations](https://slides.com/apneadiving/service-objects-waterfall-rails).
|
231
|
+
- [Handling error in Rails](https://slides.com/apneadiving/handling-error-in-ruby-rails).
|
342
232
|
|
343
233
|
Thanks
|
344
234
|
=========
|
345
|
-
Huge thanks to [
|
235
|
+
Huge thanks to [robhorrigan](https://github.com/robhorrigan) for the help during infinite naming brainstorming.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/changelog.md
ADDED
@@ -5,17 +5,6 @@ module Waterfall
|
|
5
5
|
obj.respond_to?(:is_waterfall?) && obj.is_waterfall?
|
6
6
|
end
|
7
7
|
|
8
|
-
def chained_waterfall(child_waterfall)
|
9
|
-
child_waterfall.call unless child_waterfall.flowing?
|
10
|
-
|
11
|
-
if child_waterfall.dammed?
|
12
|
-
@root.dam child_waterfall.error_pool
|
13
|
-
else
|
14
|
-
yield
|
15
|
-
end
|
16
|
-
self
|
17
|
-
end
|
18
|
-
|
19
8
|
def yield_args
|
20
9
|
[@root.outflow, @root]
|
21
10
|
end
|
@@ -16,11 +16,17 @@ module Waterfall
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def map_waterfalls(child_waterfall, mapping)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
child_waterfall.call unless child_waterfall.flowing?
|
20
|
+
|
21
|
+
mapping.each do |k, v|
|
22
|
+
@root.update_outflow(k, child_waterfall.outflow[v])
|
23
|
+
end
|
24
|
+
|
25
|
+
if child_waterfall.dammed?
|
26
|
+
@root.dam child_waterfall.error_pool
|
23
27
|
end
|
28
|
+
|
29
|
+
self
|
24
30
|
end
|
25
31
|
end
|
26
32
|
end
|
@@ -1,19 +1,10 @@
|
|
1
|
-
|
2
|
-
class WhenFalsy < Base
|
3
|
-
|
4
|
-
def initialize(root)
|
5
|
-
@root = root
|
6
|
-
end
|
1
|
+
require_relative 'when_truthy'
|
7
2
|
|
8
|
-
|
9
|
-
|
10
|
-
end
|
3
|
+
module Waterfall
|
4
|
+
class WhenFalsy < WhenTruthy
|
11
5
|
|
12
|
-
def
|
13
|
-
|
14
|
-
@root.dam yield(*yield_args)
|
15
|
-
end
|
16
|
-
@root
|
6
|
+
def condition?
|
7
|
+
! super
|
17
8
|
end
|
18
9
|
end
|
19
10
|
end
|
data/lib/waterfall/version.rb
CHANGED
data/spec/integration_spec.rb
CHANGED
@@ -7,33 +7,48 @@ describe 'Wf' do
|
|
7
7
|
|
8
8
|
it "yields wf outflow" do
|
9
9
|
wf
|
10
|
-
.chain {|outflow| outflow
|
11
|
-
.chain {|outflow| @bar = outflow
|
12
|
-
|
10
|
+
.chain {|outflow| outflow.bar = 'bar' }
|
11
|
+
.chain {|outflow| @bar = outflow.bar }
|
12
|
+
|
13
|
+
expect(wf.outflow.bar).to eq 'bar'
|
13
14
|
expect(@bar).to eq 'bar'
|
14
15
|
end
|
15
16
|
|
16
17
|
it "assigns outflow's key the value of the block" do
|
17
18
|
wf
|
18
19
|
.chain(:bar) { 'bar' }
|
19
|
-
expect(wf.outflow
|
20
|
+
expect(wf.outflow.bar).to eq 'bar'
|
20
21
|
end
|
21
22
|
|
22
23
|
context "wf internals" do
|
23
24
|
it "dam from within" do
|
24
25
|
wf
|
25
26
|
.chain {|outflow, waterfall| waterfall.dam('errrrr') }
|
26
|
-
.on_dam {|error_pool| @errors = error_pool }
|
27
27
|
|
28
28
|
expect(wf.dammed?).to be true
|
29
|
-
expect(
|
29
|
+
expect(wf.error_pool).to eq 'errrrr'
|
30
|
+
end
|
31
|
+
|
32
|
+
it "expose child waterfall outflow even if dammed (or at least what was computed)" do
|
33
|
+
wf
|
34
|
+
.chain(bar: :bar, baz: :baz) do
|
35
|
+
Wf.new
|
36
|
+
.chain(:bar) { 'bar' }
|
37
|
+
.dam('boom')
|
38
|
+
.chain(:baz) { 'baz' }
|
39
|
+
end
|
40
|
+
|
41
|
+
expect(wf.dammed?).to be true
|
42
|
+
expect(wf.error_pool).to eq 'boom'
|
43
|
+
expect(wf.outflow.bar).to eq 'bar'
|
44
|
+
expect(wf.outflow.baz).to eq nil
|
30
45
|
end
|
31
46
|
|
32
47
|
it "outflow from within" do
|
33
48
|
wf
|
34
|
-
.chain {|outflow, waterfall| waterfall.outflow
|
49
|
+
.chain {|outflow, waterfall| waterfall.outflow.foo = 1 }
|
35
50
|
|
36
|
-
expect(wf.outflow
|
51
|
+
expect(wf.outflow.foo).to eq 1
|
37
52
|
end
|
38
53
|
end
|
39
54
|
|
@@ -47,7 +62,7 @@ describe 'Wf' do
|
|
47
62
|
|
48
63
|
expect(wf.outflow.foo).to be nil
|
49
64
|
expect(wf.outflow.bar).to be nil
|
50
|
-
expect(wf.outflow
|
65
|
+
expect(wf.outflow.baz).to eq waterfall.outflow.foo
|
51
66
|
end
|
52
67
|
end
|
53
68
|
end
|
@@ -103,7 +118,6 @@ describe 'Wf' do
|
|
103
118
|
.when_falsy { my_proc.call(bool) }
|
104
119
|
.dam { 'err' }
|
105
120
|
.chain { @foo = 1 }
|
106
|
-
.on_dam { |error_pool| @error = error_pool }
|
107
121
|
end
|
108
122
|
|
109
123
|
context "main context not dammed" do
|
@@ -111,13 +125,13 @@ describe 'Wf' do
|
|
111
125
|
|
112
126
|
it "when actually falsy" do
|
113
127
|
action false
|
114
|
-
expect(
|
128
|
+
expect(wf.error_pool).to eq 'err'
|
115
129
|
expect(@foo).to_not eq 1
|
116
130
|
end
|
117
131
|
|
118
132
|
it "when actually truthy" do
|
119
133
|
action true
|
120
|
-
expect(
|
134
|
+
expect(wf.error_pool).to_not eq 'err'
|
121
135
|
expect(@foo).to eq 1
|
122
136
|
end
|
123
137
|
end
|
@@ -128,7 +142,8 @@ describe 'Wf' do
|
|
128
142
|
it "when actually falsy" do
|
129
143
|
expect(my_proc).to_not receive(:call)
|
130
144
|
action false
|
131
|
-
|
145
|
+
expect(wf.error_pool).to eq 'dammed'
|
146
|
+
end
|
132
147
|
end
|
133
148
|
end
|
134
149
|
|
@@ -141,7 +156,6 @@ describe 'Wf' do
|
|
141
156
|
.when_truthy { my_proc.call(bool) }
|
142
157
|
.dam { 'err' }
|
143
158
|
.chain { @foo = 1 }
|
144
|
-
.on_dam { |error_pool| @error = error_pool }
|
145
159
|
end
|
146
160
|
|
147
161
|
context "main context not dammed" do
|
@@ -149,13 +163,13 @@ describe 'Wf' do
|
|
149
163
|
|
150
164
|
it "when actually falsy" do
|
151
165
|
action false
|
152
|
-
expect(
|
166
|
+
expect(wf.error_pool).to_not eq 'err'
|
153
167
|
expect(@foo).to eq 1
|
154
168
|
end
|
155
169
|
|
156
170
|
it "when actually truthy" do
|
157
171
|
action true
|
158
|
-
expect(
|
172
|
+
expect(wf.error_pool).to eq 'err'
|
159
173
|
expect(@foo).to_not eq 1
|
160
174
|
end
|
161
175
|
end
|
@@ -166,6 +180,7 @@ describe 'Wf' do
|
|
166
180
|
it "when actually truthy" do
|
167
181
|
expect(my_proc).to_not receive(:call)
|
168
182
|
action true
|
183
|
+
expect(wf.error_pool).to eq 'dammed'
|
169
184
|
end
|
170
185
|
end
|
171
186
|
end
|
@@ -188,10 +203,21 @@ describe 'Wf' do
|
|
188
203
|
wf
|
189
204
|
.chain { FailingChain.new }
|
190
205
|
.chain { @foo = 1 }
|
191
|
-
.on_dam { |error_pool| @error = error_pool }
|
192
206
|
|
193
207
|
expect(@foo).to_not eq 1
|
194
|
-
expect(
|
208
|
+
expect(wf.error_pool).to eq FailingChain.error
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe "undam" do
|
213
|
+
it "flow goes back to green path" do
|
214
|
+
wf
|
215
|
+
.chain { wf.dam('err') }
|
216
|
+
.on_dam { wf.undam }
|
217
|
+
.chain { @foo = 1 }
|
218
|
+
.on_dam { raise('shouldnt happen') }
|
219
|
+
|
220
|
+
expect(@foo).to eq 1
|
195
221
|
end
|
196
222
|
end
|
197
223
|
end
|
metadata
CHANGED
@@ -1,69 +1,69 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: waterfall
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benjamin Roth
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-12-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.3'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.3'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: pry
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0.10'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0.10'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: pry-nav
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
@@ -88,13 +88,15 @@ executables: []
|
|
88
88
|
extensions: []
|
89
89
|
extra_rdoc_files: []
|
90
90
|
files:
|
91
|
-
- .gitignore
|
92
|
-
- .rspec
|
93
|
-
- .travis.yml
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rspec"
|
93
|
+
- ".travis.yml"
|
94
94
|
- Gemfile
|
95
95
|
- Gemfile.lock
|
96
96
|
- LICENSE.txt
|
97
97
|
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- changelog.md
|
98
100
|
- lib/waterfall.rb
|
99
101
|
- lib/waterfall/predicates/base.rb
|
100
102
|
- lib/waterfall/predicates/chain.rb
|
@@ -115,17 +117,17 @@ require_paths:
|
|
115
117
|
- lib
|
116
118
|
required_ruby_version: !ruby/object:Gem::Requirement
|
117
119
|
requirements:
|
118
|
-
- -
|
120
|
+
- - ">="
|
119
121
|
- !ruby/object:Gem::Version
|
120
122
|
version: '0'
|
121
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
124
|
requirements:
|
123
|
-
- -
|
125
|
+
- - ">="
|
124
126
|
- !ruby/object:Gem::Version
|
125
127
|
version: '0'
|
126
128
|
requirements: []
|
127
129
|
rubyforge_project:
|
128
|
-
rubygems_version: 2.
|
130
|
+
rubygems_version: 2.4.5
|
129
131
|
signing_key:
|
130
132
|
specification_version: 4
|
131
133
|
summary: A slice of functional programming to chain ruby services and blocks. Make
|