interactor 1.0.0 → 2.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.
- checksums.yaml +4 -4
- data/README.md +24 -1
- data/interactor.gemspec +1 -1
- data/lib/interactor.rb +24 -31
- data/lib/interactor/organizer.rb +4 -6
- data/spec/interactor/organizer_spec.rb +96 -31
- data/spec/support/lint.rb +0 -18
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61cda0fd8ba19d73ab1928d83b89f1e033ea7ece
|
4
|
+
data.tar.gz: df675488f9066ff98f40e7ef7f30becca42f57b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c5c02256cb1688170d08dcef72e7d6f390dda20a4263e1daf5f7e8596f54968ad39b0a352dd2659888c45e4360db62142cc0240100db7a8a82a96b43f3063012
|
7
|
+
data.tar.gz: d58aa049b0de1c0bf204fc8bcc4a7475a4e9158d90e3bd8ba9072a5ae15e34137e98d68ce772e945d32537d7cb525c29db1ed969cfd3f4fc4c515796cac962d3
|
data/README.md
CHANGED
@@ -242,10 +242,33 @@ Because interactors and organizers adhere to the same interface, it's trivial fo
|
|
242
242
|
|
243
243
|
If an organizer has three interactors and the second one fails, the third one is never called.
|
244
244
|
|
245
|
-
In addition to halting the chain, an organizer will also *rollback* through the interactors that it has performed so that each interactor has the opportunity to undo itself. Just define a `rollback` method. It has all the same access to the context as `perform` does.
|
245
|
+
In addition to halting the chain, an organizer will also *rollback* through the interactors that it has successfully performed so that each interactor has the opportunity to undo itself. Just define a `rollback` method. It has all the same access to the context as `perform` does.
|
246
|
+
|
247
|
+
Note that the the failed interactor itself will not be rolled back. Interactors are expected to be single-purpose, so there should be nothing to undo if the interactor fails.
|
246
248
|
|
247
249
|
## Conventions
|
248
250
|
|
251
|
+
### Good Practice
|
252
|
+
|
253
|
+
To allow rollbacks to work without fuss in organizers, interactors should only *add* to the context. They should not transform any values already in the context. For example, the following is a bad idea:
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
class FindUser
|
257
|
+
include Interactor
|
258
|
+
|
259
|
+
def perform
|
260
|
+
context[:user] = User.find(context[:user])
|
261
|
+
# Now, context[:user] contains a User object.
|
262
|
+
# Before, context[:user] held a user ID.
|
263
|
+
# This is bad.
|
264
|
+
end
|
265
|
+
end
|
266
|
+
```
|
267
|
+
|
268
|
+
If an organizer rolls back, any interactor before `FindUser` will now see a `User` object during the rollback when they were probably expecting a simple ID. This could cause problems.
|
269
|
+
|
270
|
+
### Rails
|
271
|
+
|
249
272
|
We love Rails, and we use Interactor with Rails. We put our interactors in `app/interactors` and we name them as verbs:
|
250
273
|
|
251
274
|
* `AddProductToCart`
|
data/interactor.gemspec
CHANGED
data/lib/interactor.rb
CHANGED
@@ -5,7 +5,6 @@ module Interactor
|
|
5
5
|
def self.included(base)
|
6
6
|
base.class_eval do
|
7
7
|
extend ClassMethods
|
8
|
-
include InstanceMethods
|
9
8
|
|
10
9
|
attr_reader :context
|
11
10
|
end
|
@@ -15,45 +14,39 @@ module Interactor
|
|
15
14
|
def perform(context = {})
|
16
15
|
new(context).tap(&:perform)
|
17
16
|
end
|
18
|
-
|
19
|
-
def rollback(context = {})
|
20
|
-
new(context).tap(&:rollback)
|
21
|
-
end
|
22
17
|
end
|
23
18
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
end
|
19
|
+
def initialize(context = {})
|
20
|
+
@context = Context.build(context)
|
21
|
+
setup
|
22
|
+
end
|
29
23
|
|
30
|
-
|
31
|
-
|
24
|
+
def setup
|
25
|
+
end
|
32
26
|
|
33
|
-
|
34
|
-
|
27
|
+
def perform
|
28
|
+
end
|
35
29
|
|
36
|
-
|
37
|
-
|
30
|
+
def rollback
|
31
|
+
end
|
38
32
|
|
39
|
-
|
40
|
-
|
41
|
-
|
33
|
+
def success?
|
34
|
+
context.success?
|
35
|
+
end
|
42
36
|
|
43
|
-
|
44
|
-
|
45
|
-
|
37
|
+
def failure?
|
38
|
+
context.failure?
|
39
|
+
end
|
46
40
|
|
47
|
-
|
48
|
-
|
49
|
-
|
41
|
+
def fail!(*args)
|
42
|
+
context.fail!(*args)
|
43
|
+
end
|
50
44
|
|
51
|
-
|
52
|
-
|
53
|
-
|
45
|
+
def method_missing(method, *)
|
46
|
+
context.fetch(method) { super }
|
47
|
+
end
|
54
48
|
|
55
|
-
|
56
|
-
|
57
|
-
end
|
49
|
+
def respond_to_missing?(method, *)
|
50
|
+
context.key?(method) || super
|
58
51
|
end
|
59
52
|
end
|
data/lib/interactor/organizer.rb
CHANGED
@@ -26,16 +26,14 @@ module Interactor
|
|
26
26
|
|
27
27
|
def perform
|
28
28
|
interactors.each do |interactor|
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
instance = interactor.perform(context)
|
30
|
+
rollback && break if failure?
|
31
|
+
performed << instance
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
35
|
def rollback
|
36
|
-
performed.reverse_each
|
37
|
-
interactor.rollback(context)
|
38
|
-
end
|
36
|
+
performed.reverse_each(&:rollback)
|
39
37
|
end
|
40
38
|
|
41
39
|
def performed
|
@@ -4,11 +4,11 @@ module Interactor
|
|
4
4
|
describe Organizer do
|
5
5
|
include_examples :lint
|
6
6
|
|
7
|
-
let(:
|
7
|
+
let(:organizer) { Class.new.send(:include, Organizer) }
|
8
8
|
|
9
9
|
describe ".interactors" do
|
10
10
|
it "is empty by default" do
|
11
|
-
expect(
|
11
|
+
expect(organizer.interactors).to eq([])
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -18,27 +18,27 @@ module Interactor
|
|
18
18
|
|
19
19
|
it "sets interactors given class arguments" do
|
20
20
|
expect {
|
21
|
-
|
21
|
+
organizer.organize(interactor2, interactor3)
|
22
22
|
}.to change {
|
23
|
-
|
23
|
+
organizer.interactors
|
24
24
|
}.from([]).to([interactor2, interactor3])
|
25
25
|
end
|
26
26
|
|
27
27
|
it "sets interactors given an array of classes" do
|
28
28
|
expect {
|
29
|
-
|
29
|
+
organizer.organize([interactor2, interactor3])
|
30
30
|
}.to change {
|
31
|
-
|
31
|
+
organizer.interactors
|
32
32
|
}.from([]).to([interactor2, interactor3])
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
36
|
describe "#interactors" do
|
37
37
|
let(:interactors) { double(:interactors) }
|
38
|
-
let(:instance) {
|
38
|
+
let(:instance) { organizer.new }
|
39
39
|
|
40
40
|
before do
|
41
|
-
|
41
|
+
organizer.stub(:interactors) { interactors }
|
42
42
|
end
|
43
43
|
|
44
44
|
it "defers to the class" do
|
@@ -47,20 +47,23 @@ module Interactor
|
|
47
47
|
end
|
48
48
|
|
49
49
|
describe "#perform" do
|
50
|
+
let(:instance) { organizer.new }
|
51
|
+
let(:context) { instance.context }
|
50
52
|
let(:interactor2) { double(:interactor2) }
|
51
53
|
let(:interactor3) { double(:interactor3) }
|
52
54
|
let(:interactor4) { double(:interactor4) }
|
53
|
-
let(:
|
54
|
-
let(:
|
55
|
+
let(:instance2) { double(:instance2) }
|
56
|
+
let(:instance3) { double(:instance3) }
|
57
|
+
let(:instance4) { double(:instance4) }
|
55
58
|
|
56
59
|
before do
|
57
|
-
|
60
|
+
organizer.stub(:interactors) { [interactor2, interactor3, interactor4] }
|
58
61
|
end
|
59
62
|
|
60
63
|
it "performs each interactor in order with the context" do
|
61
|
-
expect(interactor2).to receive(:perform).once.with(context).ordered
|
62
|
-
expect(interactor3).to receive(:perform).once.with(context).ordered
|
63
|
-
expect(interactor4).to receive(:perform).once.with(context).ordered
|
64
|
+
expect(interactor2).to receive(:perform).once.with(context).ordered { instance2 }
|
65
|
+
expect(interactor3).to receive(:perform).once.with(context).ordered { instance3 }
|
66
|
+
expect(interactor4).to receive(:perform).once.with(context).ordered { instance4 }
|
64
67
|
|
65
68
|
expect(instance).not_to receive(:rollback)
|
66
69
|
|
@@ -69,31 +72,34 @@ module Interactor
|
|
69
72
|
|
70
73
|
it "builds up the performed interactors" do
|
71
74
|
interactor2.stub(:perform) do
|
72
|
-
expect(instance.performed).to eq([
|
75
|
+
expect(instance.performed).to eq([])
|
76
|
+
instance2
|
73
77
|
end
|
74
78
|
|
75
79
|
interactor3.stub(:perform) do
|
76
|
-
expect(instance.performed).to eq([
|
80
|
+
expect(instance.performed).to eq([instance2])
|
81
|
+
instance3
|
77
82
|
end
|
78
83
|
|
79
84
|
interactor4.stub(:perform) do
|
80
|
-
expect(instance.performed).to eq([
|
85
|
+
expect(instance.performed).to eq([instance2, instance3])
|
86
|
+
instance4
|
81
87
|
end
|
82
88
|
|
83
89
|
expect {
|
84
90
|
instance.perform
|
85
91
|
}.to change {
|
86
92
|
instance.performed
|
87
|
-
}.from([]).to([
|
93
|
+
}.from([]).to([instance2, instance3, instance4])
|
88
94
|
end
|
89
95
|
|
90
96
|
it "aborts and rolls back on failure" do
|
91
|
-
expect(interactor2).to receive(:perform).once.with(context).ordered
|
97
|
+
expect(interactor2).to receive(:perform).once.with(context).ordered { instance2 }
|
92
98
|
expect(interactor3).to receive(:perform).once.with(context).ordered { context.fail! }
|
93
99
|
expect(interactor4).not_to receive(:perform)
|
94
100
|
|
95
101
|
expect(instance).to receive(:rollback).once.ordered do
|
96
|
-
expect(instance.performed).to eq([
|
102
|
+
expect(instance.performed).to eq([instance2])
|
97
103
|
end
|
98
104
|
|
99
105
|
instance.perform
|
@@ -101,32 +107,91 @@ module Interactor
|
|
101
107
|
end
|
102
108
|
|
103
109
|
describe "#rollback" do
|
104
|
-
let(:
|
105
|
-
let(:
|
106
|
-
let(:
|
107
|
-
let(:instance) { interactor.new }
|
108
|
-
let(:context) { instance.context }
|
110
|
+
let(:instance) { organizer.new }
|
111
|
+
let(:instance2) { double(:instance2) }
|
112
|
+
let(:instance3) { double(:instance3) }
|
109
113
|
|
110
114
|
before do
|
111
|
-
|
112
|
-
instance.stub(:performed) { [interactor2, interactor3] }
|
115
|
+
instance.stub(:performed) { [instance2, instance3] }
|
113
116
|
end
|
114
117
|
|
115
118
|
it "rolls back each performed interactor in reverse" do
|
116
|
-
expect(
|
117
|
-
expect(
|
118
|
-
expect(interactor2).to receive(:rollback).once.with(context).ordered
|
119
|
+
expect(instance3).to receive(:rollback).once.ordered
|
120
|
+
expect(instance2).to receive(:rollback).once.ordered
|
119
121
|
|
120
122
|
instance.rollback
|
121
123
|
end
|
122
124
|
end
|
123
125
|
|
124
126
|
describe "#performed" do
|
125
|
-
let(:instance) {
|
127
|
+
let(:instance) { organizer.new }
|
126
128
|
|
127
129
|
it "is empty by default" do
|
128
130
|
expect(instance.performed).to eq([])
|
129
131
|
end
|
130
132
|
end
|
133
|
+
|
134
|
+
# organizer
|
135
|
+
# ├─ organizer2
|
136
|
+
# │ ├─ interactor2a
|
137
|
+
# │ ├─ interactor2b
|
138
|
+
# │ └─ interactor2c
|
139
|
+
# ├─ interactor3
|
140
|
+
# ├─ organizer4
|
141
|
+
# │ ├─ interactor4a
|
142
|
+
# │ ├─ interactor4b
|
143
|
+
# │ └─ interactor4c
|
144
|
+
# └─ interactor5
|
145
|
+
#
|
146
|
+
context "organizers within organizers" do
|
147
|
+
let(:instance) { organizer.new }
|
148
|
+
let(:context) { instance.context }
|
149
|
+
let(:organizer2) { Class.new.send(:include, Organizer) }
|
150
|
+
let(:interactor2a) { double(:interactor2a) }
|
151
|
+
let(:interactor2b) { double(:interactor2b) }
|
152
|
+
let(:interactor2c) { double(:interactor2c) }
|
153
|
+
let(:interactor3) { double(:interactor3) }
|
154
|
+
let(:organizer4) { Class.new.send(:include, Organizer) }
|
155
|
+
let(:interactor4a) { double(:interactor4a) }
|
156
|
+
let(:interactor4b) { double(:interactor4b) }
|
157
|
+
let(:interactor4c) { double(:interactor4c) }
|
158
|
+
let(:interactor5) { double(:interactor5) }
|
159
|
+
|
160
|
+
let(:instance2a) { double(:instance2a) }
|
161
|
+
let(:instance2b) { double(:instance2b) }
|
162
|
+
let(:instance2c) { double(:instance2c) }
|
163
|
+
let(:instance3) { double(:instance3) }
|
164
|
+
let(:instance4a) { double(:instance4a) }
|
165
|
+
let(:instance4b) { double(:instance4b) }
|
166
|
+
|
167
|
+
before do
|
168
|
+
organizer.stub(:interactors) { [organizer2, interactor3, organizer4, interactor5] }
|
169
|
+
organizer2.stub(:interactors) { [interactor2a, interactor2b, interactor2c] }
|
170
|
+
organizer4.stub(:interactors) { [interactor4a, interactor4b, interactor4c] }
|
171
|
+
end
|
172
|
+
|
173
|
+
it "performs and rolls back properly" do
|
174
|
+
expect(interactor2a).to receive(:perform).once.with(context).ordered { instance2a }
|
175
|
+
expect(interactor2b).to receive(:perform).once.with(context).ordered { instance2b }
|
176
|
+
expect(interactor2c).to receive(:perform).once.with(context).ordered { instance2c }
|
177
|
+
expect(interactor3).to receive(:perform).once.with(context).ordered { instance3 }
|
178
|
+
expect(interactor4a).to receive(:perform).once.with(context).ordered { instance4a }
|
179
|
+
expect(interactor4b).to receive(:perform).once.with(context).ordered do
|
180
|
+
context.fail!
|
181
|
+
instance4b
|
182
|
+
end
|
183
|
+
expect(interactor4c).not_to receive(:perform)
|
184
|
+
expect(interactor5).not_to receive(:perform)
|
185
|
+
|
186
|
+
expect(instance4b).not_to receive(:rollback)
|
187
|
+
expect(instance4a).to receive(:rollback).once.with(no_args).ordered
|
188
|
+
expect(instance3).to receive(:rollback).once.with(no_args).ordered
|
189
|
+
expect(instance2c).to receive(:rollback).once.with(no_args).ordered
|
190
|
+
expect(instance2b).to receive(:rollback).once.with(no_args).ordered
|
191
|
+
expect(instance2a).to receive(:rollback).once.with(no_args).ordered
|
192
|
+
|
193
|
+
instance.perform
|
194
|
+
end
|
195
|
+
end
|
131
196
|
end
|
132
197
|
end
|
data/spec/support/lint.rb
CHANGED
@@ -19,24 +19,6 @@ shared_examples :lint do
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
describe ".rollback" do
|
23
|
-
let(:instance) { double(:instance) }
|
24
|
-
|
25
|
-
it "rolls back an instance with the given context" do
|
26
|
-
expect(interactor).to receive(:new).once.with(foo: "bar") { instance }
|
27
|
-
expect(instance).to receive(:rollback).once.with(no_args)
|
28
|
-
|
29
|
-
expect(interactor.rollback(foo: "bar")).to eq(instance)
|
30
|
-
end
|
31
|
-
|
32
|
-
it "provides a blank context if none is given" do
|
33
|
-
expect(interactor).to receive(:new).once.with({}) { instance }
|
34
|
-
expect(instance).to receive(:rollback).once.with(no_args)
|
35
|
-
|
36
|
-
expect(interactor.rollback).to eq(instance)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
22
|
describe ".new" do
|
41
23
|
let(:context) { double(:context) }
|
42
24
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: interactor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Collective Idea
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-08-
|
11
|
+
date: 2013-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|