dry-transaction 0.13.0 → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +308 -0
  3. data/LICENSE +20 -0
  4. data/README.md +15 -41
  5. data/dry-transaction.gemspec +35 -0
  6. data/lib/dry-transaction.rb +2 -0
  7. data/lib/dry/transaction.rb +2 -0
  8. data/lib/dry/transaction/builder.rb +6 -4
  9. data/lib/dry/transaction/callable.rb +5 -0
  10. data/lib/dry/transaction/dsl.rb +2 -0
  11. data/lib/dry/transaction/errors.rb +2 -0
  12. data/lib/dry/transaction/instance_methods.rb +6 -4
  13. data/lib/dry/transaction/operation.rb +5 -3
  14. data/lib/dry/transaction/operation_resolver.rb +2 -0
  15. data/lib/dry/transaction/result_matcher.rb +3 -1
  16. data/lib/dry/transaction/stack.rb +2 -0
  17. data/lib/dry/transaction/step.rb +6 -4
  18. data/lib/dry/transaction/step_adapter.rb +6 -4
  19. data/lib/dry/transaction/step_adapters.rb +9 -7
  20. data/lib/dry/transaction/step_adapters/around.rb +4 -2
  21. data/lib/dry/transaction/step_adapters/check.rb +2 -0
  22. data/lib/dry/transaction/step_adapters/map.rb +2 -0
  23. data/lib/dry/transaction/step_adapters/raw.rb +5 -3
  24. data/lib/dry/transaction/step_adapters/tee.rb +2 -0
  25. data/lib/dry/transaction/step_adapters/try.rb +3 -1
  26. data/lib/dry/transaction/step_failure.rb +2 -0
  27. data/lib/dry/transaction/version.rb +3 -1
  28. metadata +14 -110
  29. data/Gemfile +0 -15
  30. data/Gemfile.lock +0 -97
  31. data/LICENSE.md +0 -9
  32. data/Rakefile +0 -6
  33. data/spec/examples.txt +0 -88
  34. data/spec/integration/around_spec.rb +0 -120
  35. data/spec/integration/auto_injection_spec.rb +0 -32
  36. data/spec/integration/custom_step_adapters_spec.rb +0 -41
  37. data/spec/integration/operation_spec.rb +0 -30
  38. data/spec/integration/passing_step_arguments_spec.rb +0 -51
  39. data/spec/integration/publishing_step_events_spec.rb +0 -119
  40. data/spec/integration/transaction_spec.rb +0 -573
  41. data/spec/integration/transaction_without_steps_spec.rb +0 -101
  42. data/spec/spec_helper.rb +0 -116
  43. data/spec/support/container.rb +0 -10
  44. data/spec/support/database.rb +0 -12
  45. data/spec/support/db_transactions.rb +0 -45
  46. data/spec/support/result_mixin.rb +0 -3
  47. data/spec/support/test_module_constants.rb +0 -11
  48. data/spec/unit/step_adapters/around_spec.rb +0 -46
  49. data/spec/unit/step_adapters/check_spec.rb +0 -43
  50. data/spec/unit/step_adapters/map_spec.rb +0 -16
  51. data/spec/unit/step_adapters/raw_spec.rb +0 -36
  52. data/spec/unit/step_adapters/tee_spec.rb +0 -17
  53. data/spec/unit/step_adapters/try_spec.rb +0 -89
  54. data/spec/unit/step_spec.rb +0 -139
@@ -1,573 +0,0 @@
1
- RSpec.describe "Transactions" do
2
- include_context "database"
3
-
4
- include Dry::Monads::Result::Mixin
5
-
6
- let(:dependencies) { {} }
7
-
8
- before do
9
- container.instance_exec do
10
- register :process, -> input { {name: input["name"], email: input["email"]} }
11
- register :verify, -> input { Success(input) }
12
- register :validate, -> input { input[:email].nil? ? raise(Test::NotValidError, "email required") : input }
13
- register :persist, -> input { self[:database] << input and true }
14
- end
15
- end
16
-
17
- context "successful" do
18
- let(:transaction) {
19
- Class.new do
20
- include Dry::Transaction(container: Test::Container)
21
- map :process, with: :process
22
- step :verify, with: :verify
23
- try :validate, with: :validate, catch: Test::NotValidError
24
- tee :persist, with: :persist
25
- end.new(**dependencies)
26
- }
27
- let(:input) { {"name" => "Jane", "email" => "jane@doe.com"} }
28
-
29
- it "calls the operations" do
30
- transaction.call(input)
31
- expect(database).to include(name: "Jane", email: "jane@doe.com")
32
- end
33
-
34
- it "returns a success" do
35
- expect(transaction.call(input)).to be_a Dry::Monads::Result::Success
36
- end
37
-
38
- it "wraps the result of the final operation" do
39
- expect(transaction.call(input).value!).to eq(name: "Jane", email: "jane@doe.com")
40
- end
41
-
42
- it "can be called multiple times to the same effect" do
43
- transaction.call(input)
44
- transaction.call(input)
45
-
46
- expect(database[0]).to eql(name: "Jane", email: "jane@doe.com")
47
- expect(database[1]).to eql(name: "Jane", email: "jane@doe.com")
48
- end
49
-
50
- it "supports matching on success" do
51
- results = []
52
-
53
- transaction.call(input) do |m|
54
- m.success do |value|
55
- results << "success for #{value[:email]}"
56
- end
57
-
58
- m.failure { }
59
- end
60
-
61
- expect(results.first).to eq "success for jane@doe.com"
62
- end
63
- end
64
-
65
- context "different step names" do
66
- before do
67
- class Test::ContainerNames
68
- extend Dry::Container::Mixin
69
- register :process_step, -> input { {name: input["name"], email: input["email"]} }
70
- register :verify_step, -> input { Dry::Monads::Success(input) }
71
- register :persist_step, -> input { Test::DB << input and true }
72
- end
73
- end
74
-
75
- let(:transaction) {
76
- Class.new do
77
- include Dry::Transaction(container: Test::ContainerNames)
78
-
79
- map :process, with: :process_step
80
- step :verify, with: :verify_step
81
- tee :persist, with: :persist_step
82
- end.new(**dependencies)
83
- }
84
-
85
- it "supports steps using differently named container operations" do
86
- transaction.call("name" => "Jane", "email" => "jane@doe.com")
87
- expect(database).to include(name: "Jane", email: "jane@doe.com")
88
- end
89
- end
90
-
91
- describe "operation injection" do
92
- let(:transaction) {
93
- Class.new do
94
- include Dry::Transaction(container: Test::Container)
95
- map :process, with: :process
96
- step :verify_step, with: :verify
97
- tee :persist, with: :persist
98
- end.new(**dependencies)
99
- }
100
-
101
- let(:dependencies) {
102
- {verify_step: -> input { Success(input.merge(foo: :bar)) }}
103
- }
104
-
105
- it "calls injected operations" do
106
- transaction.call("name" => "Jane", "email" => "jane@doe.com")
107
-
108
- expect(database).to include(name: "Jane", email: "jane@doe.com", foo: :bar)
109
- end
110
- end
111
-
112
- context "wrapping operations with local methods" do
113
- let(:transaction) do
114
- Class.new do
115
- include Dry::Transaction(container: Test::Container)
116
-
117
- map :process, with: :process
118
- step :verify, with: :verify
119
- tee :persist, with: :persist
120
-
121
- def verify(input)
122
- new_input = input.merge(greeting: "hello!")
123
- super(new_input)
124
- end
125
- end.new(**dependencies)
126
- end
127
-
128
- let(:dependencies) { {} }
129
-
130
- it "allows local methods to run operations via super" do
131
- transaction.call("name" => "Jane", "email" => "jane@doe.com")
132
-
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!")
159
- end
160
- end
161
-
162
- context "operation injection of step only defined in the transaction class not in the container" do
163
- let(:transaction) do
164
- Class.new do
165
- include Dry::Transaction
166
-
167
- step :process
168
-
169
- def process(input)
170
- new_input = input << :world
171
- super(new_input)
172
- end
173
- end.new(**dependencies)
174
- end
175
-
176
- let(:dependencies) do
177
- {process: -> input { Failure(input)} }
178
- end
179
-
180
- # FIXME: needs a better description
181
- it "execute the transaction and execute the injected operation" do
182
- result = transaction.call([:hello])
183
-
184
- expect(result).to eq Failure([:hello])
185
- end
186
- end
187
-
188
- context "operation injection of step without container and no transaction instance methods" do
189
- let(:transaction) do
190
- Class.new do
191
- include Dry::Transaction
192
-
193
- map :process
194
- step :verify
195
- try :validate, catch: Test::NotValidError
196
- tee :persist
197
-
198
- end.new(**dependencies)
199
- end
200
-
201
- let(:dependencies) do
202
- {
203
- process: -> input { {name: input["name"], email: input["email"]} },
204
- verify: -> input { Success(input) },
205
- validate: -> input { input[:email].nil? ? raise(Test::NotValidError, "email required") : input },
206
- persist: -> input { database << input and true }
207
- }
208
- end
209
-
210
- let(:input) { {"name" => "Jane", "email" => "jane@doe.com"} }
211
-
212
- it "calls the injected operations" do
213
- transaction.call(input)
214
- expect(database).to include(name: "Jane", email: "jane@doe.com")
215
- end
216
- end
217
-
218
- context "operation injection of step without container and no transaction instance methods but missing an injected operation" do
219
- let(:transaction) do
220
- Class.new do
221
- include Dry::Transaction
222
-
223
- map :process
224
- step :verify
225
- try :validate, catch: Test::NotValidError
226
- tee :persist
227
-
228
- end.new(**dependencies)
229
- end
230
-
231
- let(:dependencies) do
232
- {
233
- process: -> input { {name: input["name"], email: input["email"]} },
234
- verify: -> input { Success(input) },
235
- validate: -> input { input[:email].nil? ? raise(Test::NotValidError, "email required") : input }
236
- }
237
- end
238
-
239
- let(:input) { {"name" => "Jane", "email" => "jane@doe.com"} }
240
-
241
- it "raises an exception" do
242
- expect { transaction }.to raise_error(Dry::Transaction::MissingStepError)
243
- end
244
- end
245
-
246
- context "local step definition" do
247
- let(:transaction) do
248
- Class.new do
249
- include Dry::Transaction(container: Test::Container)
250
-
251
- map :process, with: :process
252
- step :verify
253
- tee :persist, with: :persist
254
-
255
- def verify(input)
256
- Success(input.keys)
257
- end
258
- end.new
259
- end
260
-
261
- it "execute step only defined as local method" do
262
- transaction.call("name" => "Jane", "email" => "jane@doe.com")
263
-
264
- expect(database).to include([:name, :email])
265
- end
266
- end
267
-
268
- context "local step definition not in container" do
269
- let(:transaction) do
270
- Class.new do
271
- include Dry::Transaction(container: Test::Container)
272
-
273
- map :process, with: :process
274
- step :verify_only_local
275
- tee :persist, with: :persist
276
-
277
- def verify_only_local(input)
278
- Success(input.keys)
279
- end
280
- end.new
281
- end
282
-
283
- it "execute step only defined as local method" do
284
- transaction.call("name" => "Jane", "email" => "jane@doe.com")
285
-
286
- expect(database).to include([:name, :email])
287
- end
288
- end
289
-
290
- context "all steps are local methods" do
291
- let(:transaction_class) do
292
- Class.new do
293
- include Dry::Transaction
294
-
295
- map :process
296
- step :verify
297
- tee :persist
298
-
299
- def process(input)
300
- input.to_a
301
- end
302
-
303
- def verify(input)
304
- Success(input)
305
- end
306
-
307
- def persist(input)
308
- Test::Container[:database] << input and true
309
- end
310
- end
311
- end
312
-
313
- it "executes succesfully" do
314
- transaction_class.new.call("name" => "Jane", "email" => "jane@doe.com")
315
- expect(database).to include([["name", "Jane"], ["email", "jane@doe.com"]])
316
- end
317
-
318
- it "allows replacement steps to be injected" do
319
- verify = -> { Failure("nope") }
320
-
321
- result = transaction_class.new(verify: verify).("name" => "Jane", "email" => "jane@doe.com")
322
- expect(result).to eq Failure("nope")
323
- end
324
- end
325
-
326
- context "failed in a try step" do
327
- let(:transaction) {
328
- Class.new do
329
- include Dry::Transaction(container: Test::Container)
330
- map :process, with: :process
331
- step :verify, with: :verify
332
- try :validate, with: :validate, catch: Test::NotValidError
333
- tee :persist, with: :persist
334
- end.new(**dependencies)
335
- }
336
- let(:input) { {"name" => "Jane"} }
337
-
338
- it "does not run subsequent operations" do
339
- transaction.call(input)
340
- expect(database).to be_empty
341
- end
342
-
343
- it "returns a failure" do
344
- expect(transaction.call(input)).to be_a Dry::Monads::Result::Failure
345
- end
346
-
347
- it "wraps the result of the failing operation" do
348
- expect(transaction.call(input).failure).to be_a Test::NotValidError
349
- end
350
-
351
- it "supports matching on failure" do
352
- results = []
353
-
354
- transaction.call(input) do |m|
355
- m.success { }
356
-
357
- m.failure do |value|
358
- results << "Failed: #{value}"
359
- end
360
- end
361
-
362
- expect(results.first).to eq "Failed: email required"
363
- end
364
-
365
- it "supports matching on specific step failures" do
366
- results = []
367
-
368
- transaction.call(input) do |m|
369
- m.success { }
370
-
371
- m.failure :validate do |value|
372
- results << "Validation failure: #{value}"
373
- end
374
- end
375
-
376
- expect(results.first).to eq "Validation failure: email required"
377
- end
378
-
379
- it "supports matching on un-named step failures" do
380
- results = []
381
-
382
- transaction.call(input) do |m|
383
- m.success { }
384
-
385
- m.failure :some_other_step do |value|
386
- results << "Some other step failure"
387
- end
388
-
389
- m.failure do |value|
390
- results << "Catch-all failure: #{value}"
391
- end
392
- end
393
-
394
- expect(results.first).to eq "Catch-all failure: email required"
395
- end
396
- end
397
-
398
- context "failed in a raw step" do
399
- let(:input) { {"name" => "Jane", "email" => "jane@doe.com"} }
400
-
401
- before do
402
- class Test::ContainerRaw
403
- extend Dry::Container::Mixin
404
- extend Dry::Monads::Result::Mixin
405
- register :process_step, -> input { {name: input["name"], email: input["email"]} }
406
- register :verify_step, -> input { Failure("raw failure") }
407
- register :persist_step, -> input { self[:database] << input and true }
408
- end
409
- end
410
-
411
- let(:transaction) {
412
- Class.new do
413
- include Dry::Transaction(container: Test::ContainerRaw)
414
-
415
- map :process, with: :process_step
416
- step :verify, with: :verify_step
417
- tee :persist, with: :persist_step
418
- end.new(**dependencies)
419
- }
420
-
421
- it "does not run subsequent operations" do
422
- transaction.call(input)
423
- expect(database).to be_empty
424
- end
425
-
426
- it "returns a failure" do
427
- expect(transaction.call(input)).to be_a_failure
428
- end
429
-
430
- it "returns the failing value from the operation" do
431
- expect(transaction.call(input).failure).to eq "raw failure"
432
- end
433
-
434
- it "returns an object that quacks like expected" do
435
- result = transaction.call(input).failure
436
-
437
- expect(Array(result)).to eq(['raw failure'])
438
- end
439
-
440
- it "does not allow to call private methods on the result accidently" do
441
- result = transaction.call(input).failure
442
-
443
- expect { result.print('') }.to raise_error(NoMethodError)
444
- end
445
- end
446
-
447
- context "non-confirming raw step result" do
448
- let(:input) { {"name" => "Jane", "email" => "jane@doe.com"} }
449
-
450
- let(:transaction) {
451
- Class.new do
452
- include Dry::Transaction(container: Test::ContainerRaw)
453
- map :process
454
- step :verify
455
- tee :persist
456
- end.new(**dependencies)
457
- }
458
-
459
- before do
460
- class Test::ContainerRaw
461
- extend Dry::Container::Mixin
462
- register :process, -> input { {name: input["name"], email: input["email"]} }
463
- register :verify, -> input { "failure" }
464
- register :persist, -> input { Test::DB << input and true }
465
- end
466
- end
467
-
468
- it "raises an exception" do
469
- expect { transaction.call(input) }.to raise_error(ArgumentError)
470
- end
471
- end
472
-
473
- context "keyword arguments" do
474
- let(:input) { { name: 'jane', age: 20 } }
475
-
476
- let(:upcaser) do
477
- Class.new {
478
- include Dry::Monads::Result::Mixin
479
-
480
- def call(name: 'John', **rest)
481
- Success(name: name[0].upcase + name[1..-1], **rest)
482
- end
483
- }.new
484
- end
485
-
486
- let(:transaction) do
487
- Class.new {
488
- include Dry::Transaction
489
-
490
- step :camelize
491
-
492
- }.new(camelize: upcaser)
493
- end
494
-
495
- it "calls the operations" do
496
- expect(transaction.(input).value!).to eql(name: 'Jane', age: 20)
497
- end
498
- end
499
-
500
- context "invalid steps" do
501
- context "non-callable step" do
502
- context "with container" do
503
- let(:input) { {} }
504
-
505
- let(:transaction) {
506
- Class.new do
507
- include Dry::Transaction(container: Test::ContainerRaw)
508
- map :not_a_proc, with: :not_a_proc
509
- end.new
510
- }
511
-
512
- before do
513
- class Test::ContainerRaw
514
- extend Dry::Container::Mixin
515
-
516
- register :not_a_proc, "definitely not a proc"
517
- end
518
- end
519
-
520
- it "raises an exception" do
521
- expect { transaction.call(input) }.to raise_error(Dry::Transaction::InvalidStepError)
522
- end
523
- end
524
- end
525
-
526
- context "missing steps" do
527
- context "no container" do
528
- let(:input) { {} }
529
-
530
- let(:transaction) {
531
- Class.new do
532
- include Dry::Transaction
533
- map :noop
534
- map :i_am_missing
535
-
536
- def noop
537
- Success(input)
538
- end
539
- end.new
540
- }
541
-
542
- it "raises an exception" do
543
- expect { transaction.call(input) }.to raise_error(Dry::Transaction::MissingStepError)
544
- end
545
- end
546
-
547
- context "with container" do
548
- let(:input) { {} }
549
-
550
- let(:transaction) {
551
- Class.new do
552
- include Dry::Transaction(container: Test::ContainerRaw)
553
- map :noop
554
- map :i_am_missing
555
-
556
- end.new
557
- }
558
-
559
- before do
560
- class Test::ContainerRaw
561
- extend Dry::Container::Mixin
562
-
563
- register :noop, -> input { Success(input) }
564
- end
565
- end
566
-
567
- it "raises an exception" do
568
- expect { transaction.call(input) }.to raise_error(Dry::Transaction::MissingStepError)
569
- end
570
- end
571
- end
572
- end
573
- end