interaktor 0.1.3
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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +4 -0
- data/.rubocop.yml +313 -0
- data/.travis.yml +25 -0
- data/Gemfile +14 -0
- data/README.md +707 -0
- data/Rakefile +8 -0
- data/interaktor.gemspec +19 -0
- data/lib/interaktor.rb +209 -0
- data/lib/interaktor/context.rb +91 -0
- data/lib/interaktor/failure.rb +13 -0
- data/lib/interaktor/hooks.rb +264 -0
- data/lib/interaktor/organizer.rb +67 -0
- data/spec/integration_spec.rb +1786 -0
- data/spec/interactor/context_spec.rb +187 -0
- data/spec/interactor/hooks_spec.rb +358 -0
- data/spec/interactor/organizer_spec.rb +61 -0
- data/spec/interactor_spec.rb +3 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/lint.rb +136 -0
- metadata +97 -0
@@ -0,0 +1,187 @@
|
|
1
|
+
module Interaktor
|
2
|
+
describe Context do
|
3
|
+
describe ".build" do
|
4
|
+
it "converts the given hash to a context" do
|
5
|
+
context = Context.build(foo: "bar")
|
6
|
+
|
7
|
+
expect(context).to be_a(Context)
|
8
|
+
expect(context.foo).to eq("bar")
|
9
|
+
end
|
10
|
+
|
11
|
+
it "builds an empty context if no hash is given" do
|
12
|
+
context = Context.build
|
13
|
+
|
14
|
+
expect(context).to be_a(Context)
|
15
|
+
expect(context.send(:table)).to eq({})
|
16
|
+
end
|
17
|
+
|
18
|
+
it "doesn't affect the original hash" do
|
19
|
+
hash = { foo: "bar" }
|
20
|
+
context = Context.build(hash)
|
21
|
+
|
22
|
+
expect(context).to be_a(Context)
|
23
|
+
expect {
|
24
|
+
context.foo = "baz"
|
25
|
+
}.not_to change {
|
26
|
+
hash[:foo]
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
it "preserves an already built context" do
|
31
|
+
context1 = Context.build(foo: "bar")
|
32
|
+
context2 = Context.build(context1)
|
33
|
+
|
34
|
+
expect(context2).to be_a(Context)
|
35
|
+
expect {
|
36
|
+
context2.foo = "baz"
|
37
|
+
}.to change {
|
38
|
+
context1.foo
|
39
|
+
}.from("bar").to("baz")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#success?" do
|
44
|
+
let(:context) { Context.build }
|
45
|
+
|
46
|
+
it "is true by default" do
|
47
|
+
expect(context.success?).to eq(true)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#failure?" do
|
52
|
+
let(:context) { Context.build }
|
53
|
+
|
54
|
+
it "is false by default" do
|
55
|
+
expect(context.failure?).to eq(false)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#fail!" do
|
60
|
+
let(:context) { Context.build(foo: "bar") }
|
61
|
+
|
62
|
+
it "sets success to false" do
|
63
|
+
expect {
|
64
|
+
begin
|
65
|
+
context.fail!
|
66
|
+
rescue StandardError
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
}.to change(context, :success?).from(true).to(false)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "sets failure to true" do
|
73
|
+
expect {
|
74
|
+
begin
|
75
|
+
context.fail!
|
76
|
+
rescue StandardError
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
}.to change(context, :failure?).from(false).to(true)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "preserves failure" do
|
83
|
+
begin
|
84
|
+
context.fail!
|
85
|
+
rescue StandardError
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
expect {
|
90
|
+
begin
|
91
|
+
context.fail!
|
92
|
+
rescue StandardError
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
}.not_to change(context, :failure?)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "preserves the context" do
|
99
|
+
expect {
|
100
|
+
begin
|
101
|
+
context.fail!
|
102
|
+
rescue StandardError
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
}.not_to change(context, :foo)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "updates the context" do
|
109
|
+
expect {
|
110
|
+
begin
|
111
|
+
context.fail!(foo: "baz")
|
112
|
+
rescue StandardError
|
113
|
+
nil
|
114
|
+
end
|
115
|
+
}.to change(context, :foo).from("bar").to("baz")
|
116
|
+
end
|
117
|
+
|
118
|
+
it "updates the context with a string key" do
|
119
|
+
expect {
|
120
|
+
begin
|
121
|
+
context.fail!("foo" => "baz")
|
122
|
+
rescue StandardError
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
}.to change(context, :foo).from("bar").to("baz")
|
126
|
+
end
|
127
|
+
|
128
|
+
it "raises failure" do
|
129
|
+
expect {
|
130
|
+
context.fail!
|
131
|
+
}.to raise_error(Failure)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "makes the context available from the failure" do
|
135
|
+
context.fail!
|
136
|
+
rescue Failure => e
|
137
|
+
expect(e.context).to eq(context)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "#called!" do
|
142
|
+
let(:context) { Context.build }
|
143
|
+
let(:instance1) { double(:instance1) }
|
144
|
+
let(:instance2) { double(:instance2) }
|
145
|
+
|
146
|
+
it "appends to the internal list of called instances" do
|
147
|
+
expect {
|
148
|
+
context.called!(instance1)
|
149
|
+
context.called!(instance2)
|
150
|
+
}.to change(context, :_called).from([]).to([instance1, instance2])
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "#rollback!" do
|
155
|
+
let(:context) { Context.build }
|
156
|
+
let(:instance1) { double(:instance1) }
|
157
|
+
let(:instance2) { double(:instance2) }
|
158
|
+
|
159
|
+
before do
|
160
|
+
allow(context).to receive(:_called) { [instance1, instance2] }
|
161
|
+
end
|
162
|
+
|
163
|
+
it "rolls back each instance in reverse order" do
|
164
|
+
expect(instance2).to receive(:rollback).once.with(no_args).ordered
|
165
|
+
expect(instance1).to receive(:rollback).once.with(no_args).ordered
|
166
|
+
|
167
|
+
context.rollback!
|
168
|
+
end
|
169
|
+
|
170
|
+
it "ignores subsequent attempts" do
|
171
|
+
expect(instance2).to receive(:rollback).once
|
172
|
+
expect(instance1).to receive(:rollback).once
|
173
|
+
|
174
|
+
context.rollback!
|
175
|
+
context.rollback!
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "#_called" do
|
180
|
+
let(:context) { Context.build }
|
181
|
+
|
182
|
+
it "is empty by default" do
|
183
|
+
expect(context._called).to eq([])
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,358 @@
|
|
1
|
+
module Interaktor
|
2
|
+
describe Hooks do
|
3
|
+
describe "#with_hooks" do
|
4
|
+
def build_hooked(&block)
|
5
|
+
hooked = Class.new.send(:include, Interaktor::Hooks)
|
6
|
+
|
7
|
+
hooked.class_eval do
|
8
|
+
attr_reader :steps
|
9
|
+
|
10
|
+
def self.process
|
11
|
+
new.tap(&:process).steps
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@steps = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def process
|
19
|
+
with_hooks { steps << :process }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
hooked.class_eval(&block) if block
|
24
|
+
hooked
|
25
|
+
end
|
26
|
+
|
27
|
+
context "with an around hook method" do
|
28
|
+
let(:hooked) {
|
29
|
+
build_hooked do
|
30
|
+
around :add_around_before_and_around_after
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def add_around_before_and_around_after(hooked)
|
35
|
+
steps << :around_before
|
36
|
+
hooked.call
|
37
|
+
steps << :around_after
|
38
|
+
end
|
39
|
+
end
|
40
|
+
}
|
41
|
+
|
42
|
+
it "runs the around hook method" do
|
43
|
+
expect(hooked.process).to eq([
|
44
|
+
:around_before,
|
45
|
+
:process,
|
46
|
+
:around_after,
|
47
|
+
])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "with an around hook block" do
|
52
|
+
let(:hooked) {
|
53
|
+
build_hooked do
|
54
|
+
around do |hooked|
|
55
|
+
steps << :around_before
|
56
|
+
hooked.call
|
57
|
+
steps << :around_after
|
58
|
+
end
|
59
|
+
end
|
60
|
+
}
|
61
|
+
|
62
|
+
it "runs the around hook block" do
|
63
|
+
expect(hooked.process).to eq([
|
64
|
+
:around_before,
|
65
|
+
:process,
|
66
|
+
:around_after,
|
67
|
+
])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "with an around hook method and block in one call" do
|
72
|
+
let(:hooked) {
|
73
|
+
build_hooked do
|
74
|
+
around :add_around_before1_and_around_after1 do |hooked|
|
75
|
+
steps << :around_before2
|
76
|
+
hooked.call
|
77
|
+
steps << :around_after2
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def add_around_before1_and_around_after1(hooked)
|
83
|
+
steps << :around_before1
|
84
|
+
hooked.call
|
85
|
+
steps << :around_after1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
}
|
89
|
+
|
90
|
+
it "runs the around hook method and block in order" do
|
91
|
+
expect(hooked.process).to eq([
|
92
|
+
:around_before1,
|
93
|
+
:around_before2,
|
94
|
+
:process,
|
95
|
+
:around_after2,
|
96
|
+
:around_after1,
|
97
|
+
])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "with an around hook method and block in multiple calls" do
|
102
|
+
let(:hooked) {
|
103
|
+
build_hooked do
|
104
|
+
around do |hooked|
|
105
|
+
steps << :around_before1
|
106
|
+
hooked.call
|
107
|
+
steps << :around_after1
|
108
|
+
end
|
109
|
+
|
110
|
+
around :add_around_before2_and_around_after2
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def add_around_before2_and_around_after2(hooked)
|
115
|
+
steps << :around_before2
|
116
|
+
hooked.call
|
117
|
+
steps << :around_after2
|
118
|
+
end
|
119
|
+
end
|
120
|
+
}
|
121
|
+
|
122
|
+
it "runs the around hook block and method in order" do
|
123
|
+
expect(hooked.process).to eq([
|
124
|
+
:around_before1,
|
125
|
+
:around_before2,
|
126
|
+
:process,
|
127
|
+
:around_after2,
|
128
|
+
:around_after1,
|
129
|
+
])
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "with a before hook method" do
|
134
|
+
let(:hooked) {
|
135
|
+
build_hooked do
|
136
|
+
before :add_before
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def add_before
|
141
|
+
steps << :before
|
142
|
+
end
|
143
|
+
end
|
144
|
+
}
|
145
|
+
|
146
|
+
it "runs the before hook method" do
|
147
|
+
expect(hooked.process).to eq([
|
148
|
+
:before,
|
149
|
+
:process,
|
150
|
+
])
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context "with a before hook block" do
|
155
|
+
let(:hooked) {
|
156
|
+
build_hooked do
|
157
|
+
before do
|
158
|
+
steps << :before
|
159
|
+
end
|
160
|
+
end
|
161
|
+
}
|
162
|
+
|
163
|
+
it "runs the before hook block" do
|
164
|
+
expect(hooked.process).to eq([
|
165
|
+
:before,
|
166
|
+
:process,
|
167
|
+
])
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
context "with a before hook method and block in one call" do
|
172
|
+
let(:hooked) {
|
173
|
+
build_hooked do
|
174
|
+
before :add_before1 do
|
175
|
+
steps << :before2
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def add_before1
|
181
|
+
steps << :before1
|
182
|
+
end
|
183
|
+
end
|
184
|
+
}
|
185
|
+
|
186
|
+
it "runs the before hook method and block in order" do
|
187
|
+
expect(hooked.process).to eq([
|
188
|
+
:before1,
|
189
|
+
:before2,
|
190
|
+
:process,
|
191
|
+
])
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context "with a before hook method and block in multiple calls" do
|
196
|
+
let(:hooked) {
|
197
|
+
build_hooked do
|
198
|
+
before do
|
199
|
+
steps << :before1
|
200
|
+
end
|
201
|
+
|
202
|
+
before :add_before2
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
def add_before2
|
207
|
+
steps << :before2
|
208
|
+
end
|
209
|
+
end
|
210
|
+
}
|
211
|
+
|
212
|
+
it "runs the before hook block and method in order" do
|
213
|
+
expect(hooked.process).to eq([
|
214
|
+
:before1,
|
215
|
+
:before2,
|
216
|
+
:process,
|
217
|
+
])
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context "with an after hook method" do
|
222
|
+
let(:hooked) {
|
223
|
+
build_hooked do
|
224
|
+
after :add_after
|
225
|
+
|
226
|
+
private
|
227
|
+
|
228
|
+
def add_after
|
229
|
+
steps << :after
|
230
|
+
end
|
231
|
+
end
|
232
|
+
}
|
233
|
+
|
234
|
+
it "runs the after hook method" do
|
235
|
+
expect(hooked.process).to eq([
|
236
|
+
:process,
|
237
|
+
:after,
|
238
|
+
])
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
context "with an after hook block" do
|
243
|
+
let(:hooked) {
|
244
|
+
build_hooked do
|
245
|
+
after do
|
246
|
+
steps << :after
|
247
|
+
end
|
248
|
+
end
|
249
|
+
}
|
250
|
+
|
251
|
+
it "runs the after hook block" do
|
252
|
+
expect(hooked.process).to eq([
|
253
|
+
:process,
|
254
|
+
:after,
|
255
|
+
])
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
context "with an after hook method and block in one call" do
|
260
|
+
let(:hooked) {
|
261
|
+
build_hooked do
|
262
|
+
after :add_after1 do
|
263
|
+
steps << :after2
|
264
|
+
end
|
265
|
+
|
266
|
+
private
|
267
|
+
|
268
|
+
def add_after1
|
269
|
+
steps << :after1
|
270
|
+
end
|
271
|
+
end
|
272
|
+
}
|
273
|
+
|
274
|
+
it "runs the after hook method and block in order" do
|
275
|
+
expect(hooked.process).to eq([
|
276
|
+
:process,
|
277
|
+
:after2,
|
278
|
+
:after1,
|
279
|
+
])
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
context "with an after hook method and block in multiple calls" do
|
284
|
+
let(:hooked) {
|
285
|
+
build_hooked do
|
286
|
+
after do
|
287
|
+
steps << :after1
|
288
|
+
end
|
289
|
+
|
290
|
+
after :add_after2
|
291
|
+
|
292
|
+
private
|
293
|
+
|
294
|
+
def add_after2
|
295
|
+
steps << :after2
|
296
|
+
end
|
297
|
+
end
|
298
|
+
}
|
299
|
+
|
300
|
+
it "runs the after hook block and method in order" do
|
301
|
+
expect(hooked.process).to eq([
|
302
|
+
:process,
|
303
|
+
:after2,
|
304
|
+
:after1,
|
305
|
+
])
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
context "with around, before and after hooks" do
|
310
|
+
let(:hooked) {
|
311
|
+
build_hooked do
|
312
|
+
around do |hooked|
|
313
|
+
steps << :around_before1
|
314
|
+
hooked.call
|
315
|
+
steps << :around_after1
|
316
|
+
end
|
317
|
+
|
318
|
+
around do |hooked|
|
319
|
+
steps << :around_before2
|
320
|
+
hooked.call
|
321
|
+
steps << :around_after2
|
322
|
+
end
|
323
|
+
|
324
|
+
before do
|
325
|
+
steps << :before1
|
326
|
+
end
|
327
|
+
|
328
|
+
before do
|
329
|
+
steps << :before2
|
330
|
+
end
|
331
|
+
|
332
|
+
after do
|
333
|
+
steps << :after1
|
334
|
+
end
|
335
|
+
|
336
|
+
after do
|
337
|
+
steps << :after2
|
338
|
+
end
|
339
|
+
end
|
340
|
+
}
|
341
|
+
|
342
|
+
it "runs hooks in the proper order" do
|
343
|
+
expect(hooked.process).to eq([
|
344
|
+
:around_before1,
|
345
|
+
:around_before2,
|
346
|
+
:before1,
|
347
|
+
:before2,
|
348
|
+
:process,
|
349
|
+
:after2,
|
350
|
+
:after1,
|
351
|
+
:around_after2,
|
352
|
+
:around_after1,
|
353
|
+
])
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|