dry-transaction 0.10.2 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Gemfile +1 -1
- data/Gemfile.lock +20 -14
- data/lib/dry/transaction/builder.rb +2 -4
- data/lib/dry/transaction/callable.rb +38 -0
- data/lib/dry/transaction/errors.rb +27 -0
- data/lib/dry/transaction/instance_methods.rb +22 -5
- data/lib/dry/transaction/operation.rb +4 -4
- data/lib/dry/transaction/operation_resolver.rb +5 -1
- data/lib/dry/transaction/result_matcher.rb +3 -3
- data/lib/dry/transaction/stack.rb +24 -0
- data/lib/dry/transaction/step.rb +37 -21
- data/lib/dry/transaction/step_adapter.rb +49 -0
- data/lib/dry/transaction/step_adapters/around.rb +25 -0
- data/lib/dry/transaction/step_adapters/check.rb +18 -0
- data/lib/dry/transaction/step_adapters/map.rb +3 -3
- data/lib/dry/transaction/step_adapters/raw.rb +6 -12
- data/lib/dry/transaction/step_adapters/tee.rb +4 -4
- data/lib/dry/transaction/step_adapters/try.rb +11 -8
- data/lib/dry/transaction/step_adapters.rb +2 -0
- data/lib/dry/transaction/version.rb +1 -1
- data/lib/dry/transaction.rb +14 -11
- data/spec/examples.txt +81 -65
- data/spec/integration/around_spec.rb +81 -0
- data/spec/integration/custom_step_adapters_spec.rb +6 -4
- data/spec/integration/operation_spec.rb +3 -3
- data/spec/integration/passing_step_arguments_spec.rb +1 -1
- data/spec/integration/publishing_step_events_spec.rb +36 -17
- data/spec/integration/transaction_spec.rb +165 -37
- data/spec/integration/transaction_without_steps_spec.rb +101 -0
- data/spec/spec_helper.rb +14 -5
- data/spec/support/container.rb +10 -0
- data/spec/support/database.rb +12 -0
- data/spec/support/db_transactions.rb +45 -0
- data/spec/support/result_mixin.rb +3 -0
- data/spec/unit/step_adapters/around_spec.rb +46 -0
- data/spec/unit/step_adapters/check_spec.rb +43 -0
- data/spec/unit/step_adapters/map_spec.rb +5 -12
- data/spec/unit/step_adapters/raw_spec.rb +16 -32
- data/spec/unit/step_adapters/tee_spec.rb +4 -10
- data/spec/unit/step_adapters/try_spec.rb +24 -33
- data/spec/unit/step_spec.rb +41 -10
- metadata +39 -21
- data/spec/support/either_mixin.rb +0 -3
@@ -1,15 +1,16 @@
|
|
1
1
|
RSpec.describe "Transactions" do
|
2
|
+
include_context "database"
|
3
|
+
|
4
|
+
include Dry::Monads::Result::Mixin
|
5
|
+
|
2
6
|
let(:dependencies) { {} }
|
3
7
|
|
4
8
|
before do
|
5
|
-
|
6
|
-
Test::DB = []
|
7
|
-
class Test::Container
|
8
|
-
extend Dry::Container::Mixin
|
9
|
+
container.instance_exec do
|
9
10
|
register :process, -> input { {name: input["name"], email: input["email"]} }
|
10
|
-
register :verify, -> input {
|
11
|
+
register :verify, -> input { Success(input) }
|
11
12
|
register :validate, -> input { input[:email].nil? ? raise(Test::NotValidError, "email required") : input }
|
12
|
-
register :persist, -> input {
|
13
|
+
register :persist, -> input { self[:database] << input and true }
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
@@ -17,33 +18,33 @@ RSpec.describe "Transactions" do
|
|
17
18
|
let(:transaction) {
|
18
19
|
Class.new do
|
19
20
|
include Dry::Transaction(container: Test::Container)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
map :process
|
22
|
+
step :verify
|
23
|
+
try :validate, catch: Test::NotValidError
|
24
|
+
tee :persist
|
24
25
|
end.new(**dependencies)
|
25
26
|
}
|
26
27
|
let(:input) { {"name" => "Jane", "email" => "jane@doe.com"} }
|
27
28
|
|
28
29
|
it "calls the operations" do
|
29
30
|
transaction.call(input)
|
30
|
-
expect(
|
31
|
+
expect(database).to include(name: "Jane", email: "jane@doe.com")
|
31
32
|
end
|
32
33
|
|
33
34
|
it "returns a success" do
|
34
|
-
expect(transaction.call(input)).to be_a Dry::Monads::
|
35
|
+
expect(transaction.call(input)).to be_a Dry::Monads::Result::Success
|
35
36
|
end
|
36
37
|
|
37
38
|
it "wraps the result of the final operation" do
|
38
|
-
expect(transaction.call(input).value).to eq(name: "Jane", email: "jane@doe.com")
|
39
|
+
expect(transaction.call(input).value!).to eq(name: "Jane", email: "jane@doe.com")
|
39
40
|
end
|
40
41
|
|
41
42
|
it "can be called multiple times to the same effect" do
|
42
43
|
transaction.call(input)
|
43
44
|
transaction.call(input)
|
44
45
|
|
45
|
-
expect(
|
46
|
-
expect(
|
46
|
+
expect(database[0]).to eql(name: "Jane", email: "jane@doe.com")
|
47
|
+
expect(database[1]).to eql(name: "Jane", email: "jane@doe.com")
|
47
48
|
end
|
48
49
|
|
49
50
|
it "supports matching on success" do
|
@@ -66,7 +67,7 @@ RSpec.describe "Transactions" do
|
|
66
67
|
class Test::ContainerNames
|
67
68
|
extend Dry::Container::Mixin
|
68
69
|
register :process_step, -> input { {name: input["name"], email: input["email"]} }
|
69
|
-
register :verify_step, -> input { Dry::Monads::
|
70
|
+
register :verify_step, -> input { Dry::Monads::Success(input) }
|
70
71
|
register :persist_step, -> input { Test::DB << input and true }
|
71
72
|
end
|
72
73
|
end
|
@@ -83,7 +84,7 @@ RSpec.describe "Transactions" do
|
|
83
84
|
|
84
85
|
it "supports steps using differently named container operations" do
|
85
86
|
transaction.call("name" => "Jane", "email" => "jane@doe.com")
|
86
|
-
expect(
|
87
|
+
expect(database).to include(name: "Jane", email: "jane@doe.com")
|
87
88
|
end
|
88
89
|
end
|
89
90
|
|
@@ -98,13 +99,13 @@ RSpec.describe "Transactions" do
|
|
98
99
|
}
|
99
100
|
|
100
101
|
let(:dependencies) {
|
101
|
-
{verify_step: -> input {
|
102
|
+
{verify_step: -> input { Success(input.merge(foo: :bar)) }}
|
102
103
|
}
|
103
104
|
|
104
105
|
it "calls injected operations" do
|
105
106
|
transaction.call("name" => "Jane", "email" => "jane@doe.com")
|
106
107
|
|
107
|
-
expect(
|
108
|
+
expect(database).to include(name: "Jane", email: "jane@doe.com", foo: :bar)
|
108
109
|
end
|
109
110
|
end
|
110
111
|
|
@@ -129,7 +130,32 @@ RSpec.describe "Transactions" do
|
|
129
130
|
it "allows local methods to run operations via super" do
|
130
131
|
transaction.call("name" => "Jane", "email" => "jane@doe.com")
|
131
132
|
|
132
|
-
expect(
|
133
|
+
expect(database).to include(name: "Jane", email: "jane@doe.com", greeting: "hello!")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context "wrapping operations with private local methods" do
|
138
|
+
let(:transaction) do
|
139
|
+
Class.new do
|
140
|
+
include Dry::Transaction(container: Test::Container)
|
141
|
+
|
142
|
+
map :process, with: :process
|
143
|
+
step :verify, with: :verify
|
144
|
+
tee :persist, with: :persist
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def verify(input)
|
149
|
+
new_input = input.merge(greeting: "hello!")
|
150
|
+
super(new_input)
|
151
|
+
end
|
152
|
+
end.new(**dependencies)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "allows local methods to run operations via super" do
|
156
|
+
transaction.call("name" => "Jane", "email" => "jane@doe.com")
|
157
|
+
|
158
|
+
expect(database).to include(name: "Jane", email: "jane@doe.com", greeting: "hello!")
|
133
159
|
end
|
134
160
|
end
|
135
161
|
|
@@ -143,7 +169,7 @@ RSpec.describe "Transactions" do
|
|
143
169
|
tee :persist, with: :persist
|
144
170
|
|
145
171
|
def verify(input)
|
146
|
-
|
172
|
+
Success(input.keys)
|
147
173
|
end
|
148
174
|
end.new
|
149
175
|
end
|
@@ -151,7 +177,7 @@ RSpec.describe "Transactions" do
|
|
151
177
|
it "execute step only defined as local method" do
|
152
178
|
transaction.call("name" => "Jane", "email" => "jane@doe.com")
|
153
179
|
|
154
|
-
expect(
|
180
|
+
expect(database).to include([:name, :email])
|
155
181
|
end
|
156
182
|
end
|
157
183
|
|
@@ -165,7 +191,7 @@ RSpec.describe "Transactions" do
|
|
165
191
|
tee :persist, with: :persist
|
166
192
|
|
167
193
|
def verify_only_local(input)
|
168
|
-
|
194
|
+
Success(input.keys)
|
169
195
|
end
|
170
196
|
end.new
|
171
197
|
end
|
@@ -173,7 +199,7 @@ RSpec.describe "Transactions" do
|
|
173
199
|
it "execute step only defined as local method" do
|
174
200
|
transaction.call("name" => "Jane", "email" => "jane@doe.com")
|
175
201
|
|
176
|
-
expect(
|
202
|
+
expect(database).to include([:name, :email])
|
177
203
|
end
|
178
204
|
end
|
179
205
|
|
@@ -192,18 +218,18 @@ RSpec.describe "Transactions" do
|
|
192
218
|
end
|
193
219
|
|
194
220
|
def verify(input)
|
195
|
-
|
221
|
+
Success(input)
|
196
222
|
end
|
197
223
|
|
198
224
|
def persist(input)
|
199
|
-
Test::
|
225
|
+
Test::Container[:database] << input and true
|
200
226
|
end
|
201
227
|
end.new
|
202
228
|
end
|
203
229
|
|
204
230
|
it "executes succesfully" do
|
205
231
|
transaction.call("name" => "Jane", "email" => "jane@doe.com")
|
206
|
-
expect(
|
232
|
+
expect(database).to include([["name", "Jane"], ["email", "jane@doe.com"]])
|
207
233
|
end
|
208
234
|
end
|
209
235
|
|
@@ -221,15 +247,15 @@ RSpec.describe "Transactions" do
|
|
221
247
|
|
222
248
|
it "does not run subsequent operations" do
|
223
249
|
transaction.call(input)
|
224
|
-
expect(
|
250
|
+
expect(database).to be_empty
|
225
251
|
end
|
226
252
|
|
227
253
|
it "returns a failure" do
|
228
|
-
expect(transaction.call(input)).to be_a Dry::Monads::
|
254
|
+
expect(transaction.call(input)).to be_a Dry::Monads::Result::Failure
|
229
255
|
end
|
230
256
|
|
231
257
|
it "wraps the result of the failing operation" do
|
232
|
-
expect(transaction.call(input).
|
258
|
+
expect(transaction.call(input).left).to be_a Test::NotValidError
|
233
259
|
end
|
234
260
|
|
235
261
|
it "supports matching on failure" do
|
@@ -285,9 +311,10 @@ RSpec.describe "Transactions" do
|
|
285
311
|
before do
|
286
312
|
class Test::ContainerRaw
|
287
313
|
extend Dry::Container::Mixin
|
314
|
+
extend Dry::Monads::Result::Mixin
|
288
315
|
register :process_step, -> input { {name: input["name"], email: input["email"]} }
|
289
|
-
register :verify_step, -> input {
|
290
|
-
register :persist_step, -> input {
|
316
|
+
register :verify_step, -> input { Failure("raw failure") }
|
317
|
+
register :persist_step, -> input { self[:database] << input and true }
|
291
318
|
end
|
292
319
|
end
|
293
320
|
|
@@ -303,25 +330,25 @@ RSpec.describe "Transactions" do
|
|
303
330
|
|
304
331
|
it "does not run subsequent operations" do
|
305
332
|
transaction.call(input)
|
306
|
-
expect(
|
333
|
+
expect(database).to be_empty
|
307
334
|
end
|
308
335
|
|
309
336
|
it "returns a failure" do
|
310
|
-
expect(transaction.call(input)).to
|
337
|
+
expect(transaction.call(input)).to be_a_failure
|
311
338
|
end
|
312
339
|
|
313
340
|
it "returns the failing value from the operation" do
|
314
|
-
expect(transaction.call(input).
|
341
|
+
expect(transaction.call(input).left).to eq "raw failure"
|
315
342
|
end
|
316
343
|
|
317
344
|
it "returns an object that quacks like expected" do
|
318
|
-
result = transaction.call(input).
|
345
|
+
result = transaction.call(input).left
|
319
346
|
|
320
347
|
expect(Array(result)).to eq(['raw failure'])
|
321
348
|
end
|
322
349
|
|
323
350
|
it "does not allow to call private methods on the result accidently" do
|
324
|
-
result = transaction.call(input).
|
351
|
+
result = transaction.call(input).left
|
325
352
|
|
326
353
|
expect { result.print('') }.to raise_error(NoMethodError)
|
327
354
|
end
|
@@ -352,4 +379,105 @@ RSpec.describe "Transactions" do
|
|
352
379
|
expect { transaction.call(input) }.to raise_error(ArgumentError)
|
353
380
|
end
|
354
381
|
end
|
382
|
+
|
383
|
+
context "keyword arguments" do
|
384
|
+
let(:input) { { name: 'jane', age: 20 } }
|
385
|
+
|
386
|
+
let(:upcaser) do
|
387
|
+
Class.new {
|
388
|
+
include Dry::Monads::Result::Mixin
|
389
|
+
|
390
|
+
def call(name: 'John', **rest)
|
391
|
+
Success(name: name[0].upcase + name[1..-1], **rest)
|
392
|
+
end
|
393
|
+
}.new
|
394
|
+
end
|
395
|
+
|
396
|
+
let(:transaction) do
|
397
|
+
Class.new {
|
398
|
+
include Dry::Transaction
|
399
|
+
|
400
|
+
step :camelize
|
401
|
+
|
402
|
+
}.new(camelize: upcaser)
|
403
|
+
end
|
404
|
+
|
405
|
+
it "calls the operations" do
|
406
|
+
expect(transaction.(input).value).to eql(name: 'Jane', age: 20)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
context "invalid steps" do
|
411
|
+
context "non-callable step" do
|
412
|
+
context "with container" do
|
413
|
+
let(:input) { {} }
|
414
|
+
|
415
|
+
let(:transaction) {
|
416
|
+
Class.new do
|
417
|
+
include Dry::Transaction(container: Test::ContainerRaw)
|
418
|
+
map :not_a_proc
|
419
|
+
end.new
|
420
|
+
}
|
421
|
+
|
422
|
+
before do
|
423
|
+
class Test::ContainerRaw
|
424
|
+
extend Dry::Container::Mixin
|
425
|
+
|
426
|
+
register :not_a_proc, "definitely not a proc"
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
it "raises an exception" do
|
431
|
+
expect { transaction.call(input) }.to raise_error(Dry::Transaction::InvalidStepError)
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
context "missing steps" do
|
437
|
+
context "no container" do
|
438
|
+
let(:input) { {} }
|
439
|
+
|
440
|
+
let(:transaction) {
|
441
|
+
Class.new do
|
442
|
+
include Dry::Transaction
|
443
|
+
map :noop
|
444
|
+
map :i_am_missing
|
445
|
+
|
446
|
+
def noop
|
447
|
+
Success(input)
|
448
|
+
end
|
449
|
+
end.new
|
450
|
+
}
|
451
|
+
|
452
|
+
it "raises an exception" do
|
453
|
+
expect { transaction.call(input) }.to raise_error(Dry::Transaction::MissingStepError)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
context "with container" do
|
458
|
+
let(:input) { {} }
|
459
|
+
|
460
|
+
let(:transaction) {
|
461
|
+
Class.new do
|
462
|
+
include Dry::Transaction(container: Test::ContainerRaw)
|
463
|
+
map :noop
|
464
|
+
map :i_am_missing
|
465
|
+
|
466
|
+
end.new
|
467
|
+
}
|
468
|
+
|
469
|
+
before do
|
470
|
+
class Test::ContainerRaw
|
471
|
+
extend Dry::Container::Mixin
|
472
|
+
|
473
|
+
register :noop, -> input { Success(input) }
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
it "raises an exception" do
|
478
|
+
expect { transaction.call(input) }.to raise_error(Dry::Transaction::MissingStepError)
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
355
483
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
RSpec.describe "Transactions steps without arguments" do
|
2
|
+
let(:dependencies) { {} }
|
3
|
+
|
4
|
+
before do
|
5
|
+
Test::NotValidError = Class.new(StandardError)
|
6
|
+
Test::DB = [{"name" => "Jane", "email" => "jane@doe.com"}]
|
7
|
+
Test::Http = Class.new do
|
8
|
+
def self.get
|
9
|
+
"pong"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.post(value)
|
13
|
+
Test::DB << value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
class Test::Container
|
17
|
+
extend Dry::Container::Mixin
|
18
|
+
register :fetch_data, -> { Test::DB.delete_at(0) }, call: false
|
19
|
+
register :call_outside, -> { Test::Http.get }, call: false
|
20
|
+
register :external_store, -> input { Test::Http.post(input) }
|
21
|
+
register :process, -> input { { name: input["name"], email: input["email"] } }
|
22
|
+
register :validate, -> input { input[:email].nil? ? raise(Test::NotValidError, "email required") : input }
|
23
|
+
register :persist, -> input { Test::DB << input and true }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "successful" do
|
28
|
+
let(:transaction) {
|
29
|
+
Class.new do
|
30
|
+
include Dry::Transaction(container: Test::Container)
|
31
|
+
map :fetch_data
|
32
|
+
map :process
|
33
|
+
try :validate, catch: Test::NotValidError
|
34
|
+
tee :persist
|
35
|
+
end.new(**dependencies)
|
36
|
+
}
|
37
|
+
|
38
|
+
it "calls the operations" do
|
39
|
+
transaction.call
|
40
|
+
expect(Test::DB).to include(name: "Jane", email: "jane@doe.com")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns a success" do
|
44
|
+
expect(transaction.call()).to be_a Dry::Monads::Result::Success
|
45
|
+
end
|
46
|
+
|
47
|
+
it "wraps the result of the final operation" do
|
48
|
+
expect(transaction.call().value!).to eq(name: "Jane", email: "jane@doe.com")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "supports matching on success" do
|
52
|
+
results = []
|
53
|
+
|
54
|
+
transaction.call() do |m|
|
55
|
+
m.success do |value|
|
56
|
+
results << "success for #{value[:email]}"
|
57
|
+
end
|
58
|
+
|
59
|
+
m.failure { }
|
60
|
+
end
|
61
|
+
|
62
|
+
expect(results.first).to eq "success for jane@doe.com"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "using multiple tee step operators" do
|
67
|
+
let(:transaction) {
|
68
|
+
Class.new do
|
69
|
+
include Dry::Transaction(container: Test::Container)
|
70
|
+
tee :call_outside
|
71
|
+
map :fetch_data
|
72
|
+
map :process
|
73
|
+
try :validate, catch: Test::NotValidError
|
74
|
+
tee :external_store
|
75
|
+
end.new(**dependencies)
|
76
|
+
}
|
77
|
+
|
78
|
+
it "calls the operations" do
|
79
|
+
transaction.call
|
80
|
+
expect(Test::DB).to include(name: "Jane", email: "jane@doe.com")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "not needing arguments in the middle of the transaction" do
|
85
|
+
let(:transaction) {
|
86
|
+
Class.new do
|
87
|
+
include Dry::Transaction(container: Test::Container)
|
88
|
+
map :process
|
89
|
+
try :validate, catch: Test::NotValidError
|
90
|
+
tee :call_outside
|
91
|
+
tee :external_store
|
92
|
+
end.new(**dependencies)
|
93
|
+
}
|
94
|
+
let(:input) { {"name" => "Jane", "email" => "jane@doe.com"} }
|
95
|
+
|
96
|
+
it "calls the operations" do
|
97
|
+
transaction.call(input)
|
98
|
+
expect(Test::DB).to include(name: "Jane", email: "jane@doe.com")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,12 +1,19 @@
|
|
1
|
-
if RUBY_ENGINE ==
|
2
|
-
require
|
3
|
-
|
4
|
-
|
1
|
+
if RUBY_ENGINE == 'ruby' && ENV['COVERAGE'] == 'true'
|
2
|
+
require 'yaml'
|
3
|
+
rubies = YAML.load(File.read(File.join(__dir__, '..', '.travis.yml')))['rvm']
|
4
|
+
latest_mri = rubies.select { |v| v =~ /\A\d+\.\d+.\d+\z/ }.max
|
5
|
+
|
6
|
+
if RUBY_VERSION == latest_mri
|
7
|
+
require 'simplecov'
|
8
|
+
SimpleCov.start do
|
9
|
+
add_filter '/spec/'
|
10
|
+
end
|
5
11
|
end
|
6
12
|
end
|
7
13
|
|
8
14
|
begin
|
9
|
-
require "
|
15
|
+
require "pry"
|
16
|
+
require "pry-byebug"
|
10
17
|
rescue LoadError; end
|
11
18
|
|
12
19
|
require "dry-transaction"
|
@@ -104,4 +111,6 @@ RSpec.configure do |config|
|
|
104
111
|
# test failures related to randomization by passing the same `--seed` value
|
105
112
|
# as the one that triggered the failure.
|
106
113
|
Kernel.srand config.seed
|
114
|
+
|
115
|
+
config.include Dry::Monads::Result::Mixin, adapter: true
|
107
116
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
RSpec.shared_context "db transactions" do
|
2
|
+
include_context "database"
|
3
|
+
|
4
|
+
before do
|
5
|
+
Test::Rollback = Class.new(StandardError)
|
6
|
+
|
7
|
+
class << Test::DB
|
8
|
+
attr_accessor :in_transaction, :rolled_back, :committed
|
9
|
+
alias_method :in_transaction?, :in_transaction
|
10
|
+
alias_method :rolled_back?, :rolled_back
|
11
|
+
alias_method :committed?, :committed
|
12
|
+
|
13
|
+
def transaction
|
14
|
+
self.in_transaction = true
|
15
|
+
self.rolled_back = false
|
16
|
+
self.committed = false
|
17
|
+
|
18
|
+
yield.tap do
|
19
|
+
self.committed = true
|
20
|
+
end
|
21
|
+
rescue => error
|
22
|
+
self.rolled_back = true
|
23
|
+
clear
|
24
|
+
|
25
|
+
raise error
|
26
|
+
ensure
|
27
|
+
self.in_transaction = false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
container.register(:transaction) do |input, &block|
|
32
|
+
result = nil
|
33
|
+
|
34
|
+
begin
|
35
|
+
Test::DB.transaction do
|
36
|
+
result = block.(Success(input))
|
37
|
+
raise Test::Rollback if result.failure?
|
38
|
+
result
|
39
|
+
end
|
40
|
+
rescue Test::Rollback
|
41
|
+
result
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
RSpec.describe Dry::Transaction::StepAdapters::Around, :adapter do
|
2
|
+
subject { described_class.new }
|
3
|
+
|
4
|
+
let(:operation) {
|
5
|
+
-> (input, &block) { block.(Success(input.upcase)) }
|
6
|
+
}
|
7
|
+
|
8
|
+
let(:options) { { step_name: "unit" } }
|
9
|
+
|
10
|
+
let(:continue) do
|
11
|
+
-> (input) { input.fmap { |v| v + " terminated" } }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#call" do
|
15
|
+
context "when the result of the operation is NOT a Dry::Monads::Result" do
|
16
|
+
let(:continue) do
|
17
|
+
-> (input) { "plain string" }
|
18
|
+
end
|
19
|
+
|
20
|
+
it "raises an InvalidResultError" do
|
21
|
+
expect {
|
22
|
+
subject.(operation, options, ["input"], &continue)
|
23
|
+
}.to raise_error(
|
24
|
+
Dry::Transaction::InvalidResultError,
|
25
|
+
"step +unit+ must return a Result object"
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "passing a block" do
|
31
|
+
it "returns a Success value with result from block" do
|
32
|
+
expect(subject.(operation, options, ["input"], &continue)).to eql(Success("INPUT terminated"))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when the result of the operation is a Failure value" do
|
37
|
+
let(:operation) {
|
38
|
+
-> (input, &block) { block.(Failure(input.upcase)) }
|
39
|
+
}
|
40
|
+
|
41
|
+
it "return a Failure value" do
|
42
|
+
expect(subject.(operation, options, ["input"], &continue)).to eql(Failure("INPUT"))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
RSpec.describe Dry::Transaction::StepAdapters::Check, :adapter do
|
2
|
+
|
3
|
+
subject { described_class.new }
|
4
|
+
|
5
|
+
let(:operation) {
|
6
|
+
-> (input) { input == "right" }
|
7
|
+
}
|
8
|
+
|
9
|
+
let(:options) { { step_name: "unit" } }
|
10
|
+
|
11
|
+
describe "#call" do
|
12
|
+
|
13
|
+
it "returns the result of the operation as output" do
|
14
|
+
expect(subject.(operation, options, ["right"])).to eql(Success("right"))
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when check fails" do
|
18
|
+
it "return a Failure" do
|
19
|
+
expect(subject.(operation, options, ["wrong"])).to eql(Failure("wrong"))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when operation return right monad" do
|
24
|
+
let(:operation) {
|
25
|
+
-> (input) { Success(true) }
|
26
|
+
}
|
27
|
+
|
28
|
+
it "return a Success" do
|
29
|
+
expect(subject.(operation, options, ["input"])).to eql(Success("input"))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when operation return left monad" do
|
34
|
+
let(:operation) {
|
35
|
+
-> (input) { Failure(true) }
|
36
|
+
}
|
37
|
+
|
38
|
+
it "return a Failure" do
|
39
|
+
expect(subject.(operation, options, ["input"])).to eql(Failure("input"))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1,23 +1,16 @@
|
|
1
|
-
RSpec.describe Dry::Transaction::StepAdapters::Map do
|
1
|
+
RSpec.describe Dry::Transaction::StepAdapters::Map, :adapter do
|
2
2
|
|
3
3
|
subject { described_class.new }
|
4
4
|
|
5
|
+
let(:options) { {} }
|
6
|
+
|
5
7
|
let(:operation) {
|
6
8
|
-> (input) { input.upcase }
|
7
9
|
}
|
8
10
|
|
9
|
-
let(:step) {
|
10
|
-
Dry::Transaction::Step.new(subject, :step, :step, operation, {})
|
11
|
-
}
|
12
|
-
|
13
11
|
describe "#call" do
|
14
|
-
|
15
|
-
|
16
|
-
expect(subject.call(step, 'input')).to be_a Dry::Monads::Either::Right
|
17
|
-
end
|
18
|
-
|
19
|
-
it "return the result of the operation as output" do
|
20
|
-
expect(subject.call(step, 'input').value).to eql 'INPUT'
|
12
|
+
it "return a Success value" do
|
13
|
+
expect(subject.(operation, options, 'input')).to eql(Success('INPUT'))
|
21
14
|
end
|
22
15
|
end
|
23
16
|
end
|