interactor 2.1.1 → 3.0.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.
data/interactor.gemspec CHANGED
@@ -2,19 +2,18 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "interactor"
5
- spec.version = "2.1.1"
5
+ spec.version = "3.0.0"
6
6
 
7
7
  spec.author = "Collective Idea"
8
8
  spec.email = "info@collectiveidea.com"
9
- spec.description = "Interactor provides a common interface for performing complex interactions in a single request."
9
+ spec.description = "Interactor provides a common interface for performing complex user interactions."
10
10
  spec.summary = "Simple interactor implementation"
11
11
  spec.homepage = "https://github.com/collectiveidea/interactor"
12
12
  spec.license = "MIT"
13
13
 
14
- spec.files = `git ls-files`.split($/)
15
- spec.test_files = spec.files.grep(/^spec/)
16
- spec.require_paths = ["lib"]
14
+ spec.files = `git ls-files`.split($/)
15
+ spec.test_files = spec.files.grep(/^spec/)
17
16
 
18
- spec.add_development_dependency "bundler", "~> 1.3"
19
- spec.add_development_dependency "rake", "~> 10.1"
17
+ spec.add_development_dependency "bundler", "~> 1.7"
18
+ spec.add_development_dependency "rake", "~> 10.3"
20
19
  end
data/lib/interactor.rb CHANGED
@@ -1,54 +1,50 @@
1
1
  require "interactor/context"
2
+ require "interactor/error"
3
+ require "interactor/hooks"
2
4
  require "interactor/organizer"
3
5
 
4
6
  module Interactor
5
7
  def self.included(base)
6
8
  base.class_eval do
7
9
  extend ClassMethods
10
+ include Hooks
8
11
 
9
12
  attr_reader :context
10
13
  end
11
14
  end
12
15
 
13
16
  module ClassMethods
14
- def perform(context = {})
15
- new(context).tap do |instance|
16
- instance.perform unless instance.failure?
17
- end
17
+ def call(context = {})
18
+ new(context).tap(&:run).context
19
+ end
20
+
21
+ def call!(context = {})
22
+ new(context).tap(&:run!).context
18
23
  end
19
24
  end
20
25
 
21
26
  def initialize(context = {})
22
27
  @context = Context.build(context)
23
- setup
24
- end
25
-
26
- def setup
27
28
  end
28
29
 
29
- def perform
30
- end
31
-
32
- def rollback
30
+ def run
31
+ run!
32
+ rescue Failure
33
33
  end
34
34
 
35
- def success?
36
- context.success?
37
- end
38
-
39
- def failure?
40
- context.failure?
41
- end
42
-
43
- def fail!(*args)
44
- context.fail!(*args)
35
+ def run!
36
+ with_hooks do
37
+ call
38
+ context.called!(self)
39
+ end
40
+ rescue
41
+ context.rollback!
42
+ raise
45
43
  end
46
44
 
47
- def method_missing(method, *)
48
- context.fetch(method) { context.fetch(method.to_s) { super } }
45
+ def call
49
46
  end
50
47
 
51
- def respond_to_missing?(method, *)
52
- (context && (context.key?(method) || context.key?(method.to_s))) || super
48
+ def rollback
53
49
  end
54
50
  end
@@ -1,13 +1,9 @@
1
- require "delegate"
1
+ require "ostruct"
2
2
 
3
3
  module Interactor
4
- class Context < SimpleDelegator
4
+ class Context < OpenStruct
5
5
  def self.build(context = {})
6
- self === context ? context : new(context.dup)
7
- end
8
-
9
- def initialize(context = {})
10
- super(context)
6
+ self === context ? context : new(context)
11
7
  end
12
8
 
13
9
  def success?
@@ -19,8 +15,23 @@ module Interactor
19
15
  end
20
16
 
21
17
  def fail!(context = {})
22
- update(context)
18
+ modifiable.update(context)
23
19
  @failure = true
20
+ raise Failure, self
21
+ end
22
+
23
+ def called!(interactor)
24
+ _called << interactor
25
+ end
26
+
27
+ def rollback!
28
+ return false if @rolled_back
29
+ _called.reverse_each(&:rollback)
30
+ @rolled_back = true
31
+ end
32
+
33
+ def _called
34
+ @called ||= []
24
35
  end
25
36
  end
26
37
  end
@@ -0,0 +1,10 @@
1
+ module Interactor
2
+ class Failure < StandardError
3
+ attr_reader :context
4
+
5
+ def initialize(context = nil)
6
+ @context = context
7
+ super
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,53 @@
1
+ module Interactor
2
+ module Hooks
3
+ def self.included(base)
4
+ base.class_eval do
5
+ extend ClassMethods
6
+ end
7
+ end
8
+
9
+ module ClassMethods
10
+ def before(*hooks, &block)
11
+ hooks << block if block
12
+ hooks.each { |hook| before_hooks.push(hook) }
13
+ end
14
+
15
+ def after(*hooks, &block)
16
+ hooks << block if block
17
+ hooks.each { |hook| after_hooks.unshift(hook) }
18
+ end
19
+
20
+ def before_hooks
21
+ @before_hooks ||= []
22
+ end
23
+
24
+ def after_hooks
25
+ @after_hooks ||= []
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def with_hooks
32
+ run_before_hooks
33
+ yield
34
+ run_after_hooks
35
+ end
36
+
37
+ def run_before_hooks
38
+ run_hooks(self.class.before_hooks)
39
+ end
40
+
41
+ def run_after_hooks
42
+ run_hooks(self.class.after_hooks)
43
+ end
44
+
45
+ def run_hooks(hooks)
46
+ hooks.each { |hook| run_hook(hook) }
47
+ end
48
+
49
+ def run_hook(hook)
50
+ hook.is_a?(Symbol) ? send(hook) : instance_eval(&hook)
51
+ end
52
+ end
53
+ end
@@ -10,41 +10,21 @@ module Interactor
10
10
  end
11
11
 
12
12
  module ClassMethods
13
- def interactors
14
- @interactors ||= []
13
+ def organize(*interactors)
14
+ @organized = interactors.flatten
15
15
  end
16
16
 
17
- def organize(*interactors)
18
- @interactors = interactors.flatten
17
+ def organized
18
+ @organized ||= []
19
19
  end
20
20
  end
21
21
 
22
22
  module InstanceMethods
23
- def interactors
24
- self.class.interactors
25
- end
26
-
27
- def perform
28
- interactors.each do |interactor|
29
- begin
30
- instance = interactor.perform(context)
31
- rescue
32
- rollback
33
- raise
34
- end
35
-
36
- rollback && break if failure?
37
- performed << instance
23
+ def call
24
+ self.class.organized.each do |interactor|
25
+ interactor.call!(context)
38
26
  end
39
27
  end
40
-
41
- def rollback
42
- performed.reverse_each(&:rollback)
43
- end
44
-
45
- def performed
46
- @performed ||= []
47
- end
48
28
  end
49
29
  end
50
30
  end
@@ -0,0 +1,967 @@
1
+ describe "Integration" do
2
+ def build_interactor(&block)
3
+ interactor = Class.new.send(:include, Interactor)
4
+ interactor.class_eval(&block) if block
5
+ interactor
6
+ end
7
+
8
+ def build_organizer(options = {}, &block)
9
+ organizer = Class.new.send(:include, Interactor::Organizer)
10
+ organizer.organize(options[:organize]) if options[:organize]
11
+ organizer.class_eval(&block) if block
12
+ organizer
13
+ end
14
+
15
+ # organizer
16
+ # ├─ organizer2
17
+ # │ ├─ interactor2a
18
+ # │ ├─ interactor2b
19
+ # │ └─ interactor2c
20
+ # ├─ interactor3
21
+ # ├─ organizer4
22
+ # │ ├─ interactor4a
23
+ # │ ├─ interactor4b
24
+ # │ └─ interactor4c
25
+ # └─ interactor5
26
+
27
+ let(:organizer) {
28
+ build_organizer(organize: [organizer2, interactor3, organizer4, interactor5]) do
29
+ before do
30
+ context.steps << :before
31
+ end
32
+
33
+ after do
34
+ context.steps << :after
35
+ end
36
+ end
37
+ }
38
+
39
+ let(:organizer2) {
40
+ build_organizer(organize: [interactor2a, interactor2b, interactor2c]) do
41
+ before do
42
+ context.steps << :before2
43
+ end
44
+
45
+ after do
46
+ context.steps << :after2
47
+ end
48
+ end
49
+ }
50
+
51
+ let(:interactor2a) {
52
+ build_interactor do
53
+ before do
54
+ context.steps << :before2a
55
+ end
56
+
57
+ after do
58
+ context.steps << :after2a
59
+ end
60
+
61
+ def call
62
+ context.steps << :call2a
63
+ end
64
+
65
+ def rollback
66
+ context.steps << :rollback2a
67
+ end
68
+ end
69
+ }
70
+
71
+ let(:interactor2b) {
72
+ build_interactor do
73
+ before do
74
+ context.steps << :before2b
75
+ end
76
+
77
+ after do
78
+ context.steps << :after2b
79
+ end
80
+
81
+ def call
82
+ context.steps << :call2b
83
+ end
84
+
85
+ def rollback
86
+ context.steps << :rollback2b
87
+ end
88
+ end
89
+ }
90
+
91
+ let(:interactor2c) {
92
+ build_interactor do
93
+ before do
94
+ context.steps << :before2c
95
+ end
96
+
97
+ after do
98
+ context.steps << :after2c
99
+ end
100
+
101
+ def call
102
+ context.steps << :call2c
103
+ end
104
+
105
+ def rollback
106
+ context.steps << :rollback2c
107
+ end
108
+ end
109
+ }
110
+
111
+ let(:interactor3) {
112
+ build_interactor do
113
+ before do
114
+ context.steps << :before3
115
+ end
116
+
117
+ after do
118
+ context.steps << :after3
119
+ end
120
+
121
+ def call
122
+ context.steps << :call3
123
+ end
124
+
125
+ def rollback
126
+ context.steps << :rollback3
127
+ end
128
+ end
129
+ }
130
+
131
+ let(:organizer4) {
132
+ build_organizer(organize: [interactor4a, interactor4b, interactor4c]) do
133
+ before do
134
+ context.steps << :before4
135
+ end
136
+
137
+ after do
138
+ context.steps << :after4
139
+ end
140
+ end
141
+ }
142
+
143
+ let(:interactor4a) {
144
+ build_interactor do
145
+ before do
146
+ context.steps << :before4a
147
+ end
148
+
149
+ after do
150
+ context.steps << :after4a
151
+ end
152
+
153
+ def call
154
+ context.steps << :call4a
155
+ end
156
+
157
+ def rollback
158
+ context.steps << :rollback4a
159
+ end
160
+ end
161
+ }
162
+
163
+ let(:interactor4b) {
164
+ build_interactor do
165
+ before do
166
+ context.steps << :before4b
167
+ end
168
+
169
+ after do
170
+ context.steps << :after4b
171
+ end
172
+
173
+ def call
174
+ context.steps << :call4b
175
+ end
176
+
177
+ def rollback
178
+ context.steps << :rollback4b
179
+ end
180
+ end
181
+ }
182
+
183
+ let(:interactor4c) {
184
+ build_interactor do
185
+ before do
186
+ context.steps << :before4c
187
+ end
188
+
189
+ after do
190
+ context.steps << :after4c
191
+ end
192
+
193
+ def call
194
+ context.steps << :call4c
195
+ end
196
+
197
+ def rollback
198
+ context.steps << :rollback4c
199
+ end
200
+ end
201
+ }
202
+
203
+ let(:interactor5) {
204
+ build_interactor do
205
+ before do
206
+ context.steps << :before5
207
+ end
208
+
209
+ after do
210
+ context.steps << :after5
211
+ end
212
+
213
+ def call
214
+ context.steps << :call5
215
+ end
216
+
217
+ def rollback
218
+ context.steps << :rollback5
219
+ end
220
+ end
221
+ }
222
+
223
+ let(:context) { Interactor::Context.build(steps: []) }
224
+
225
+ context "when successful" do
226
+ it "calls and runs hooks in the proper sequence" do
227
+ expect {
228
+ organizer.call(context)
229
+ }.to change {
230
+ context.steps
231
+ }.from([]).to([
232
+ :before,
233
+ :before2,
234
+ :before2a, :call2a, :after2a,
235
+ :before2b, :call2b, :after2b,
236
+ :before2c, :call2c, :after2c,
237
+ :after2,
238
+ :before3, :call3, :after3,
239
+ :before4,
240
+ :before4a, :call4a, :after4a,
241
+ :before4b, :call4b, :after4b,
242
+ :before4c, :call4c, :after4c,
243
+ :after4,
244
+ :before5, :call5, :after5,
245
+ :after
246
+ ])
247
+ end
248
+ end
249
+
250
+ context "when a before hook fails" do
251
+ let(:organizer) {
252
+ build_organizer(organize: [organizer2, interactor3, organizer4, interactor5]) do
253
+ before do
254
+ context.fail!
255
+ context.steps << :before
256
+ end
257
+
258
+ after do
259
+ context.steps << :after
260
+ end
261
+ end
262
+ }
263
+
264
+ it "aborts" do
265
+ expect {
266
+ organizer.call(context)
267
+ }.not_to change {
268
+ context.steps
269
+ }
270
+ end
271
+ end
272
+
273
+ context "when a before hook errors" do
274
+ let(:organizer) {
275
+ build_organizer(organize: [organizer2, interactor3, organizer4, interactor5]) do
276
+ before do
277
+ raise "foo"
278
+ context.steps << :before
279
+ end
280
+
281
+ after do
282
+ context.steps << :after
283
+ end
284
+ end
285
+ }
286
+
287
+ it "aborts" do
288
+ expect {
289
+ organizer.call(context) rescue nil
290
+ }.not_to change {
291
+ context.steps
292
+ }
293
+ end
294
+
295
+ it "raises the error" do
296
+ expect {
297
+ organizer.call(context)
298
+ }.to raise_error("foo")
299
+ end
300
+ end
301
+
302
+ context "when an after hook fails" do
303
+ let(:organizer) {
304
+ build_organizer(organize: [organizer2, interactor3, organizer4, interactor5]) do
305
+ before do
306
+ context.steps << :before
307
+ end
308
+
309
+ after do
310
+ context.fail!
311
+ context.steps << :after
312
+ end
313
+ end
314
+ }
315
+
316
+ it "rolls back successfully called interactors" do
317
+ expect {
318
+ organizer.call(context)
319
+ }.to change {
320
+ context.steps
321
+ }.from([]).to([
322
+ :before,
323
+ :before2,
324
+ :before2a, :call2a, :after2a,
325
+ :before2b, :call2b, :after2b,
326
+ :before2c, :call2c, :after2c,
327
+ :after2,
328
+ :before3, :call3, :after3,
329
+ :before4,
330
+ :before4a, :call4a, :after4a,
331
+ :before4b, :call4b, :after4b,
332
+ :before4c, :call4c, :after4c,
333
+ :after4,
334
+ :before5, :call5, :after5,
335
+ :rollback5,
336
+ :rollback4c,
337
+ :rollback4b,
338
+ :rollback4a,
339
+ :rollback3,
340
+ :rollback2c,
341
+ :rollback2b,
342
+ :rollback2a
343
+ ])
344
+ end
345
+ end
346
+
347
+ context "when an after hook errors" do
348
+ let(:organizer) {
349
+ build_organizer(organize: [organizer2, interactor3, organizer4, interactor5]) do
350
+ before do
351
+ context.steps << :before
352
+ end
353
+
354
+ after do
355
+ raise "foo"
356
+ context.steps << :after
357
+ end
358
+ end
359
+ }
360
+
361
+ it "aborts" do
362
+ expect {
363
+ organizer.call(context) rescue nil
364
+ }.to change {
365
+ context.steps
366
+ }.from([]).to([
367
+ :before,
368
+ :before2,
369
+ :before2a, :call2a, :after2a,
370
+ :before2b, :call2b, :after2b,
371
+ :before2c, :call2c, :after2c,
372
+ :after2,
373
+ :before3, :call3, :after3,
374
+ :before4,
375
+ :before4a, :call4a, :after4a,
376
+ :before4b, :call4b, :after4b,
377
+ :before4c, :call4c, :after4c,
378
+ :after4,
379
+ :before5, :call5, :after5,
380
+ :rollback5,
381
+ :rollback4c,
382
+ :rollback4b,
383
+ :rollback4a,
384
+ :rollback3,
385
+ :rollback2c,
386
+ :rollback2b,
387
+ :rollback2a
388
+ ])
389
+ end
390
+
391
+ it "raises the error" do
392
+ expect {
393
+ organizer.call(context)
394
+ }.to raise_error("foo")
395
+ end
396
+ end
397
+
398
+ context "when a nested before hook fails" do
399
+ let(:interactor3) {
400
+ build_interactor do
401
+ before do
402
+ context.fail!
403
+ context.steps << :before3
404
+ end
405
+
406
+ after do
407
+ context.steps << :after3
408
+ end
409
+
410
+ def call
411
+ context.steps << :call3
412
+ end
413
+
414
+ def rollback
415
+ context.steps << :rollback3
416
+ end
417
+ end
418
+ }
419
+
420
+ it "rolls back successfully called interactors" do
421
+ expect {
422
+ organizer.call(context)
423
+ }.to change {
424
+ context.steps
425
+ }.from([]).to([
426
+ :before,
427
+ :before2,
428
+ :before2a, :call2a, :after2a,
429
+ :before2b, :call2b, :after2b,
430
+ :before2c, :call2c, :after2c,
431
+ :after2,
432
+ :rollback2c,
433
+ :rollback2b,
434
+ :rollback2a
435
+ ])
436
+ end
437
+ end
438
+
439
+ context "when a nested before hook errors" do
440
+ let(:interactor3) {
441
+ build_interactor do
442
+ before do
443
+ raise "foo"
444
+ context.steps << :before3
445
+ end
446
+
447
+ after do
448
+ context.steps << :after3
449
+ end
450
+
451
+ def call
452
+ context.steps << :call3
453
+ end
454
+
455
+ def rollback
456
+ context.steps << :rollback3
457
+ end
458
+ end
459
+ }
460
+
461
+ it "rolls back successfully called interactors" do
462
+ expect {
463
+ organizer.call(context) rescue nil
464
+ }.to change {
465
+ context.steps
466
+ }.from([]).to([
467
+ :before,
468
+ :before2,
469
+ :before2a, :call2a, :after2a,
470
+ :before2b, :call2b, :after2b,
471
+ :before2c, :call2c, :after2c,
472
+ :after2,
473
+ :rollback2c,
474
+ :rollback2b,
475
+ :rollback2a
476
+ ])
477
+ end
478
+
479
+ it "raises the error" do
480
+ expect {
481
+ organizer.call(context)
482
+ }.to raise_error("foo")
483
+ end
484
+ end
485
+
486
+ context "when a nested call fails" do
487
+ let(:interactor3) {
488
+ build_interactor do
489
+ before do
490
+ context.steps << :before3
491
+ end
492
+
493
+ after do
494
+ context.steps << :after3
495
+ end
496
+
497
+ def call
498
+ context.fail!
499
+ context.steps << :call3
500
+ end
501
+
502
+ def rollback
503
+ context.steps << :rollback3
504
+ end
505
+ end
506
+ }
507
+
508
+ it "rolls back successfully called interactors" do
509
+ expect {
510
+ organizer.call(context)
511
+ }.to change {
512
+ context.steps
513
+ }.from([]).to([
514
+ :before,
515
+ :before2,
516
+ :before2a, :call2a, :after2a,
517
+ :before2b, :call2b, :after2b,
518
+ :before2c, :call2c, :after2c,
519
+ :after2,
520
+ :before3,
521
+ :rollback2c,
522
+ :rollback2b,
523
+ :rollback2a
524
+ ])
525
+ end
526
+ end
527
+
528
+ context "when a nested call errors" do
529
+ let(:interactor3) {
530
+ build_interactor do
531
+ before do
532
+ context.steps << :before3
533
+ end
534
+
535
+ after do
536
+ context.steps << :after3
537
+ end
538
+
539
+ def call
540
+ raise "foo"
541
+ context.steps << :call3
542
+ end
543
+
544
+ def rollback
545
+ context.steps << :rollback3
546
+ end
547
+ end
548
+ }
549
+
550
+ it "rolls back successfully called interactors" do
551
+ expect {
552
+ organizer.call(context) rescue nil
553
+ }.to change {
554
+ context.steps
555
+ }.from([]).to([
556
+ :before,
557
+ :before2,
558
+ :before2a, :call2a, :after2a,
559
+ :before2b, :call2b, :after2b,
560
+ :before2c, :call2c, :after2c,
561
+ :after2,
562
+ :before3,
563
+ :rollback2c,
564
+ :rollback2b,
565
+ :rollback2a
566
+ ])
567
+ end
568
+
569
+ it "raises the error" do
570
+ expect {
571
+ organizer.call(context)
572
+ }.to raise_error("foo")
573
+ end
574
+ end
575
+
576
+ context "when a nested after hook fails" do
577
+ let(:interactor3) {
578
+ build_interactor do
579
+ before do
580
+ context.steps << :before3
581
+ end
582
+
583
+ after do
584
+ context.fail!
585
+ context.steps << :after3
586
+ end
587
+
588
+ def call
589
+ context.steps << :call3
590
+ end
591
+
592
+ def rollback
593
+ context.steps << :rollback3
594
+ end
595
+ end
596
+ }
597
+
598
+ it "rolls back successfully called interactors and the failed interactor" do
599
+ expect {
600
+ organizer.call(context)
601
+ }.to change {
602
+ context.steps
603
+ }.from([]).to([
604
+ :before,
605
+ :before2,
606
+ :before2a, :call2a, :after2a,
607
+ :before2b, :call2b, :after2b,
608
+ :before2c, :call2c, :after2c,
609
+ :after2,
610
+ :before3, :call3,
611
+ :rollback3,
612
+ :rollback2c,
613
+ :rollback2b,
614
+ :rollback2a
615
+ ])
616
+ end
617
+ end
618
+
619
+ context "when a nested after hook errors" do
620
+ let(:interactor3) {
621
+ build_interactor do
622
+ before do
623
+ context.steps << :before3
624
+ end
625
+
626
+ after do
627
+ raise "foo"
628
+ context.steps << :after3
629
+ end
630
+
631
+ def call
632
+ context.steps << :call3
633
+ end
634
+
635
+ def rollback
636
+ context.steps << :rollback3
637
+ end
638
+ end
639
+ }
640
+
641
+ it "rolls back successfully called interactors and the failed interactor" do
642
+ expect {
643
+ organizer.call(context) rescue nil
644
+ }.to change {
645
+ context.steps
646
+ }.from([]).to([
647
+ :before,
648
+ :before2,
649
+ :before2a, :call2a, :after2a,
650
+ :before2b, :call2b, :after2b,
651
+ :before2c, :call2c, :after2c,
652
+ :after2,
653
+ :before3, :call3,
654
+ :rollback3,
655
+ :rollback2c,
656
+ :rollback2b,
657
+ :rollback2a
658
+ ])
659
+ end
660
+
661
+ it "raises the error" do
662
+ expect {
663
+ organizer.call(context)
664
+ }.to raise_error("foo")
665
+ end
666
+ end
667
+
668
+ context "when a deeply nested before hook fails" do
669
+ let(:interactor4b) {
670
+ build_interactor do
671
+ before do
672
+ context.fail!
673
+ context.steps << :before4b
674
+ end
675
+
676
+ after do
677
+ context.steps << :after4b
678
+ end
679
+
680
+ def call
681
+ context.steps << :call4b
682
+ end
683
+
684
+ def rollback
685
+ context.steps << :rollback4b
686
+ end
687
+ end
688
+ }
689
+
690
+ it "rolls back successfully called interactors" do
691
+ expect {
692
+ organizer.call(context)
693
+ }.to change {
694
+ context.steps
695
+ }.from([]).to([
696
+ :before,
697
+ :before2,
698
+ :before2a, :call2a, :after2a,
699
+ :before2b, :call2b, :after2b,
700
+ :before2c, :call2c, :after2c,
701
+ :after2,
702
+ :before3, :call3, :after3,
703
+ :before4,
704
+ :before4a, :call4a, :after4a,
705
+ :rollback4a,
706
+ :rollback3,
707
+ :rollback2c,
708
+ :rollback2b,
709
+ :rollback2a
710
+ ])
711
+ end
712
+ end
713
+
714
+ context "when a deeply nested before hook errors" do
715
+ let(:interactor4b) {
716
+ build_interactor do
717
+ before do
718
+ raise "foo"
719
+ context.steps << :before4b
720
+ end
721
+
722
+ after do
723
+ context.steps << :after4b
724
+ end
725
+
726
+ def call
727
+ context.steps << :call4b
728
+ end
729
+
730
+ def rollback
731
+ context.steps << :rollback4b
732
+ end
733
+ end
734
+ }
735
+
736
+ it "rolls back successfully called interactors" do
737
+ expect {
738
+ organizer.call(context) rescue nil
739
+ }.to change {
740
+ context.steps
741
+ }.from([]).to([
742
+ :before,
743
+ :before2,
744
+ :before2a, :call2a, :after2a,
745
+ :before2b, :call2b, :after2b,
746
+ :before2c, :call2c, :after2c,
747
+ :after2,
748
+ :before3, :call3, :after3,
749
+ :before4,
750
+ :before4a, :call4a, :after4a,
751
+ :rollback4a,
752
+ :rollback3,
753
+ :rollback2c,
754
+ :rollback2b,
755
+ :rollback2a
756
+ ])
757
+ end
758
+
759
+ it "raises the error" do
760
+ expect {
761
+ organizer.call(context)
762
+ }.to raise_error("foo")
763
+ end
764
+ end
765
+
766
+ context "when a deeply nested call fails" do
767
+ let(:interactor4b) {
768
+ build_interactor do
769
+ before do
770
+ context.steps << :before4b
771
+ end
772
+
773
+ after do
774
+ context.steps << :after4b
775
+ end
776
+
777
+ def call
778
+ context.fail!
779
+ context.steps << :call4b
780
+ end
781
+
782
+ def rollback
783
+ context.steps << :rollback4b
784
+ end
785
+ end
786
+ }
787
+
788
+ it "rolls back successfully called interactors" do
789
+ expect {
790
+ organizer.call(context)
791
+ }.to change {
792
+ context.steps
793
+ }.from([]).to([
794
+ :before,
795
+ :before2,
796
+ :before2a, :call2a, :after2a,
797
+ :before2b, :call2b, :after2b,
798
+ :before2c, :call2c, :after2c,
799
+ :after2,
800
+ :before3, :call3, :after3,
801
+ :before4,
802
+ :before4a, :call4a, :after4a,
803
+ :before4b,
804
+ :rollback4a,
805
+ :rollback3,
806
+ :rollback2c,
807
+ :rollback2b,
808
+ :rollback2a
809
+ ])
810
+ end
811
+ end
812
+
813
+ context "when a deeply nested call errors" do
814
+ let(:interactor4b) {
815
+ build_interactor do
816
+ before do
817
+ context.steps << :before4b
818
+ end
819
+
820
+ after do
821
+ context.steps << :after4b
822
+ end
823
+
824
+ def call
825
+ raise "foo"
826
+ context.steps << :call4b
827
+ end
828
+
829
+ def rollback
830
+ context.steps << :rollback4b
831
+ end
832
+ end
833
+ }
834
+
835
+ it "rolls back successfully called interactors" do
836
+ expect {
837
+ organizer.call(context) rescue nil
838
+ }.to change {
839
+ context.steps
840
+ }.from([]).to([
841
+ :before,
842
+ :before2,
843
+ :before2a, :call2a, :after2a,
844
+ :before2b, :call2b, :after2b,
845
+ :before2c, :call2c, :after2c,
846
+ :after2,
847
+ :before3, :call3, :after3,
848
+ :before4,
849
+ :before4a, :call4a, :after4a,
850
+ :before4b,
851
+ :rollback4a,
852
+ :rollback3,
853
+ :rollback2c,
854
+ :rollback2b,
855
+ :rollback2a
856
+ ])
857
+ end
858
+
859
+ it "raises the error" do
860
+ expect {
861
+ organizer.call(context)
862
+ }.to raise_error("foo")
863
+ end
864
+ end
865
+
866
+ context "when a deeply nested after hook fails" do
867
+ let(:interactor4b) {
868
+ build_interactor do
869
+ before do
870
+ context.steps << :before4b
871
+ end
872
+
873
+ after do
874
+ context.fail!
875
+ context.steps << :after4b
876
+ end
877
+
878
+ def call
879
+ context.steps << :call4b
880
+ end
881
+
882
+ def rollback
883
+ context.steps << :rollback4b
884
+ end
885
+ end
886
+ }
887
+
888
+ it "rolls back successfully called interactors and the failed interactor" do
889
+ expect {
890
+ organizer.call(context)
891
+ }.to change {
892
+ context.steps
893
+ }.from([]).to([
894
+ :before,
895
+ :before2,
896
+ :before2a, :call2a, :after2a,
897
+ :before2b, :call2b, :after2b,
898
+ :before2c, :call2c, :after2c,
899
+ :after2,
900
+ :before3, :call3, :after3,
901
+ :before4,
902
+ :before4a, :call4a, :after4a,
903
+ :before4b, :call4b,
904
+ :rollback4b,
905
+ :rollback4a,
906
+ :rollback3,
907
+ :rollback2c,
908
+ :rollback2b,
909
+ :rollback2a
910
+ ])
911
+ end
912
+ end
913
+
914
+ context "when a deeply nested after hook errors" do
915
+ let(:interactor4b) {
916
+ build_interactor do
917
+ before do
918
+ context.steps << :before4b
919
+ end
920
+
921
+ after do
922
+ raise "foo"
923
+ context.steps << :after4b
924
+ end
925
+
926
+ def call
927
+ context.steps << :call4b
928
+ end
929
+
930
+ def rollback
931
+ context.steps << :rollback4b
932
+ end
933
+ end
934
+ }
935
+
936
+ it "rolls back successfully called interactors and the failed interactor" do
937
+ expect {
938
+ organizer.call(context) rescue nil
939
+ }.to change {
940
+ context.steps
941
+ }.from([]).to([
942
+ :before,
943
+ :before2,
944
+ :before2a, :call2a, :after2a,
945
+ :before2b, :call2b, :after2b,
946
+ :before2c, :call2c, :after2c,
947
+ :after2,
948
+ :before3, :call3, :after3,
949
+ :before4,
950
+ :before4a, :call4a, :after4a,
951
+ :before4b, :call4b,
952
+ :rollback4b,
953
+ :rollback4a,
954
+ :rollback3,
955
+ :rollback2c,
956
+ :rollback2b,
957
+ :rollback2a
958
+ ])
959
+ end
960
+
961
+ it "raises the error" do
962
+ expect {
963
+ organizer.call(context)
964
+ }.to raise_error("foo")
965
+ end
966
+ end
967
+ end