interactor 2.1.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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