interactor 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6832397e4ab11a93ded8ded31c4e47519be8fe34
4
- data.tar.gz: 3ffdfed6582a46b97745356133e5b34a2c3d3b0c
3
+ metadata.gz: 61cda0fd8ba19d73ab1928d83b89f1e033ea7ece
4
+ data.tar.gz: df675488f9066ff98f40e7ef7f30becca42f57b1
5
5
  SHA512:
6
- metadata.gz: e744650c5033bab652b44d122fc7a1b785d7ab8d22e9afc654a2b1f2e1071f8fdf1846e7c3c68e3163cdd2892ea1ad8d98c37ef9bf8b75c8351b21f13d04215f
7
- data.tar.gz: 81ba3295b6dc2cf3991919708b46cb7e83d1f964799f9b6262e46bfbc78733c337f823bc4401c3fec6822154ddf7ccc0abdc63167cc15865ca5f01453f5381df
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`
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "interactor"
5
- spec.version = "1.0.0"
5
+ spec.version = "2.0.0"
6
6
 
7
7
  spec.author = "Collective Idea"
8
8
  spec.email = "info@collectiveidea.com"
@@ -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
- module InstanceMethods
25
- def initialize(context = {})
26
- @context = Context.build(context)
27
- setup
28
- end
19
+ def initialize(context = {})
20
+ @context = Context.build(context)
21
+ setup
22
+ end
29
23
 
30
- def setup
31
- end
24
+ def setup
25
+ end
32
26
 
33
- def perform
34
- end
27
+ def perform
28
+ end
35
29
 
36
- def rollback
37
- end
30
+ def rollback
31
+ end
38
32
 
39
- def success?
40
- context.success?
41
- end
33
+ def success?
34
+ context.success?
35
+ end
42
36
 
43
- def failure?
44
- context.failure?
45
- end
37
+ def failure?
38
+ context.failure?
39
+ end
46
40
 
47
- def fail!(*args)
48
- context.fail!(*args)
49
- end
41
+ def fail!(*args)
42
+ context.fail!(*args)
43
+ end
50
44
 
51
- def method_missing(method, *)
52
- context.fetch(method) { super }
53
- end
45
+ def method_missing(method, *)
46
+ context.fetch(method) { super }
47
+ end
54
48
 
55
- def respond_to_missing?(method, *)
56
- context.key?(method) || super
57
- end
49
+ def respond_to_missing?(method, *)
50
+ context.key?(method) || super
58
51
  end
59
52
  end
@@ -26,16 +26,14 @@ module Interactor
26
26
 
27
27
  def perform
28
28
  interactors.each do |interactor|
29
- performed << interactor
30
- interactor.perform(context)
31
- rollback && break if context.failure?
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 do |interactor|
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(:interactor) { Class.new.send(:include, Organizer) }
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(interactor.interactors).to eq([])
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
- interactor.organize(interactor2, interactor3)
21
+ organizer.organize(interactor2, interactor3)
22
22
  }.to change {
23
- interactor.interactors
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
- interactor.organize([interactor2, interactor3])
29
+ organizer.organize([interactor2, interactor3])
30
30
  }.to change {
31
- interactor.interactors
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) { interactor.new }
38
+ let(:instance) { organizer.new }
39
39
 
40
40
  before do
41
- interactor.stub(:interactors) { interactors }
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(:instance) { interactor.new }
54
- let(:context) { instance.context }
55
+ let(:instance2) { double(:instance2) }
56
+ let(:instance3) { double(:instance3) }
57
+ let(:instance4) { double(:instance4) }
55
58
 
56
59
  before do
57
- interactor.stub(:interactors) { [interactor2, interactor3, interactor4] }
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([interactor2])
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([interactor2, interactor3])
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([interactor2, interactor3, interactor4])
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([interactor2, interactor3, interactor4])
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([interactor2, interactor3])
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(:interactor2) { double(:interactor2) }
105
- let(:interactor3) { double(:interactor3) }
106
- let(:interactor4) { double(:interactor4) }
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
- interactor.stub(:interactors) { [interactor2, interactor3, interactor4] }
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(interactor4).not_to receive(:rollback)
117
- expect(interactor3).to receive(:rollback).once.with(context).ordered
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) { interactor.new }
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
@@ -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: 1.0.0
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-17 00:00:00.000000000 Z
11
+ date: 2013-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler