light-service 0.8.4 → 0.10.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/.travis.yml +5 -2
- data/README.md +149 -52
- data/RELEASES.md +6 -0
- data/gemfiles/activesupport_3.gemfile +1 -1
- data/gemfiles/activesupport_4.gemfile +1 -1
- data/gemfiles/activesupport_5.gemfile +1 -1
- data/lib/light-service.rb +7 -0
- data/lib/light-service/action.rb +22 -2
- data/lib/light-service/orchestrator.rb +21 -3
- data/lib/light-service/organizer.rb +42 -20
- data/lib/light-service/organizer/execute.rb +14 -0
- data/lib/light-service/organizer/iterate.rb +22 -0
- data/lib/light-service/organizer/reduce_if.rb +17 -0
- data/lib/light-service/organizer/reduce_until.rb +20 -0
- data/lib/light-service/organizer/scoped_reducable.rb +13 -0
- data/lib/light-service/organizer/verify_call_method_exists.rb +28 -0
- data/lib/light-service/organizer/with_callback.rb +26 -0
- data/lib/light-service/organizer/with_reducer.rb +14 -4
- data/lib/light-service/organizer/with_reducer_factory.rb +2 -0
- data/lib/light-service/version.rb +1 -1
- data/light-service.gemspec +2 -2
- data/resources/orchestrators_deprecated.svg +10 -0
- data/spec/acceptance/add_numbers_spec.rb +3 -3
- data/spec/acceptance/after_actions_spec.rb +67 -0
- data/spec/acceptance/before_actions_spec.rb +109 -0
- data/spec/acceptance/orchestrator/context_failure_and_skipping_spec.rb +12 -10
- data/spec/acceptance/orchestrator/execute_spec.rb +8 -6
- data/spec/acceptance/orchestrator/iterate_spec.rb +9 -7
- data/spec/acceptance/orchestrator/organizer_action_combination_spec.rb +13 -11
- data/spec/acceptance/orchestrator/reduce_if_spec.rb +9 -7
- data/spec/acceptance/orchestrator/reduce_until_spec.rb +7 -5
- data/spec/acceptance/orchestrator/with_callback_spec.rb +8 -6
- data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +65 -0
- data/spec/acceptance/organizer/execute_spec.rb +46 -0
- data/spec/acceptance/organizer/iterate_spec.rb +37 -0
- data/spec/acceptance/organizer/reduce_if_spec.rb +51 -0
- data/spec/acceptance/organizer/reduce_until_spec.rb +43 -0
- data/spec/acceptance/organizer/with_callback_spec.rb +110 -0
- data/spec/organizer/with_reducer_spec.rb +2 -7
- data/spec/sample/calculates_tax_spec.rb +1 -4
- data/spec/spec_helper.rb +12 -0
- data/spec/support.rb +9 -0
- data/spec/test_doubles.rb +84 -17
- metadata +34 -6
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
RSpec.describe LightService::Organizer do
|
5
|
+
class TestExecute
|
6
|
+
extend LightService::Organizer
|
7
|
+
|
8
|
+
def self.call(context)
|
9
|
+
with(context).reduce(steps)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.steps
|
13
|
+
[
|
14
|
+
TestDoubles::AddsOneAction,
|
15
|
+
execute(->(ctx) { ctx.number += 1 }),
|
16
|
+
execute(->(ctx) { ctx[:something] = 'hello' }),
|
17
|
+
TestDoubles::AddsOneAction
|
18
|
+
]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:empty_context) { LightService::Context.make }
|
23
|
+
|
24
|
+
it 'calls the lambda in the execute block using the context' do
|
25
|
+
result = TestExecute.call(:number => 0)
|
26
|
+
|
27
|
+
expect(result).to be_success
|
28
|
+
expect(result.number).to eq(3)
|
29
|
+
expect(result[:something]).to eq('hello')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'will not execute a failed context' do
|
33
|
+
empty_context.fail!('Something bad happened')
|
34
|
+
|
35
|
+
result = TestExecute.call(empty_context)
|
36
|
+
|
37
|
+
expect(result).to be_failure
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'does not execute over a skipped context' do
|
41
|
+
empty_context.skip_remaining!('No more needed')
|
42
|
+
|
43
|
+
result = TestExecute.call(empty_context)
|
44
|
+
expect(result).to be_success
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
RSpec.describe LightService::Organizer do
|
5
|
+
let(:empty_context) { LightService::Context.make }
|
6
|
+
|
7
|
+
it 'reduces each item of a collection and singularizes the collection key' do
|
8
|
+
result = TestDoubles::TestIterate.call(:number => 1,
|
9
|
+
:counters => [1, 2, 3, 4])
|
10
|
+
|
11
|
+
expect(result).to be_success
|
12
|
+
expect(result.number).to eq(5)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'accepts a single action or organizer' do
|
16
|
+
result = TestDoubles::TestIterate.call_single(:number => 1,
|
17
|
+
:counters => [1, 2, 3, 4])
|
18
|
+
|
19
|
+
expect(result).to be_success
|
20
|
+
expect(result.number).to eq(5)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'will not iterate over a failed context' do
|
24
|
+
empty_context.fail!('Something bad happened')
|
25
|
+
|
26
|
+
result = TestDoubles::TestIterate.call(empty_context)
|
27
|
+
|
28
|
+
expect(result).to be_failure
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'does not iterate over a skipped context' do
|
32
|
+
empty_context.skip_remaining!('No more needed')
|
33
|
+
|
34
|
+
result = TestDoubles::TestIterate.call(empty_context)
|
35
|
+
expect(result).to be_success
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
RSpec.describe LightService::Organizer do
|
5
|
+
class TestReduceIf
|
6
|
+
extend LightService::Organizer
|
7
|
+
|
8
|
+
def self.call(context)
|
9
|
+
with(context).reduce(actions)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.actions
|
13
|
+
[
|
14
|
+
TestDoubles::AddsOneAction,
|
15
|
+
reduce_if(->(ctx) { ctx.number == 1 },
|
16
|
+
TestDoubles::AddsOneAction)
|
17
|
+
]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:empty_context) { LightService::Context.make }
|
22
|
+
|
23
|
+
it 'reduces if the block evaluates to true' do
|
24
|
+
result = TestReduceIf.call(:number => 0)
|
25
|
+
|
26
|
+
expect(result).to be_success
|
27
|
+
expect(result[:number]).to eq(2)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'does not reduce if the block evaluates to false' do
|
31
|
+
result = TestReduceIf.call(:number => 2)
|
32
|
+
|
33
|
+
expect(result).to be_success
|
34
|
+
expect(result[:number]).to eq(3)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'will not reduce over a failed context' do
|
38
|
+
empty_context.fail!('Something bad happened')
|
39
|
+
|
40
|
+
result = TestReduceIf.call(empty_context)
|
41
|
+
|
42
|
+
expect(result).to be_failure
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'does not reduce over a skipped context' do
|
46
|
+
empty_context.skip_remaining!('No more needed')
|
47
|
+
|
48
|
+
result = TestReduceIf.call(empty_context)
|
49
|
+
expect(result).to be_success
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
RSpec.describe LightService::Organizer do
|
5
|
+
class TestReduceUntil
|
6
|
+
extend LightService::Organizer
|
7
|
+
|
8
|
+
def self.call(context)
|
9
|
+
with(context).reduce(actions)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.actions
|
13
|
+
[
|
14
|
+
reduce_until(->(ctx) { ctx.number == 3 },
|
15
|
+
TestDoubles::AddsOneAction)
|
16
|
+
]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:empty_context) { LightService::Context.make }
|
21
|
+
|
22
|
+
it 'reduces until the block evaluates to true' do
|
23
|
+
context = { :number => 1 }
|
24
|
+
result = TestReduceUntil.call(context)
|
25
|
+
|
26
|
+
expect(result).to be_success
|
27
|
+
expect(result.number).to eq(3)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'does not execute on failed context' do
|
31
|
+
empty_context.fail!('Something bad happened')
|
32
|
+
|
33
|
+
result = TestReduceUntil.call(empty_context)
|
34
|
+
expect(result).to be_failure
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'does not execute a skipped context' do
|
38
|
+
empty_context.skip_remaining!('No more needed')
|
39
|
+
|
40
|
+
result = TestReduceUntil.call(empty_context)
|
41
|
+
expect(result).to be_success
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
RSpec.describe LightService::Organizer do
|
5
|
+
describe 'a simple case with a single callback' do
|
6
|
+
it 'calls the actions defined with callback' do
|
7
|
+
result = TestDoubles::TestWithCallback.call
|
8
|
+
|
9
|
+
expect(result.counter).to eq(3)
|
10
|
+
expect(result.total).to eq(6)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'a more complex example with nested callbacks' do
|
15
|
+
class TestWithNestedCallback
|
16
|
+
extend LightService::Organizer
|
17
|
+
|
18
|
+
def self.call(context = {})
|
19
|
+
with(context).reduce(actions)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.actions
|
23
|
+
[
|
24
|
+
SetUpNestedContextAction,
|
25
|
+
with_callback(IterateOuterCollectionAction,
|
26
|
+
[IncrementOuterCountAction,
|
27
|
+
with_callback(TestDoubles::IterateCollectionAction,
|
28
|
+
[TestDoubles::IncrementCountAction,
|
29
|
+
TestDoubles::AddToTotalAction])])
|
30
|
+
]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class SetUpNestedContextAction
|
35
|
+
extend LightService::Action
|
36
|
+
promises :outer_numbers, :outer_counter,
|
37
|
+
:numbers, :counter, :total
|
38
|
+
|
39
|
+
executed do |ctx|
|
40
|
+
ctx.outer_numbers = [12, 17]
|
41
|
+
ctx.outer_counter = 0
|
42
|
+
ctx.numbers = [1, 2, 3]
|
43
|
+
ctx.counter = 0
|
44
|
+
ctx.total = 0
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class IterateOuterCollectionAction
|
49
|
+
extend LightService::Action
|
50
|
+
expects :outer_numbers, :callback
|
51
|
+
promises :outer_number
|
52
|
+
|
53
|
+
executed do |ctx|
|
54
|
+
ctx.outer_numbers.each do |outer_number|
|
55
|
+
ctx.outer_number = outer_number
|
56
|
+
ctx.callback.call(ctx)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class IncrementOuterCountAction
|
62
|
+
extend LightService::Action
|
63
|
+
expects :outer_counter
|
64
|
+
|
65
|
+
executed do |ctx|
|
66
|
+
ctx.outer_counter = ctx.outer_counter + 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'calls both the action and the nested callbacks' do
|
71
|
+
result = TestWithNestedCallback.call
|
72
|
+
|
73
|
+
expect(result.outer_counter).to eq(2)
|
74
|
+
# Counts and total are the duplicates of
|
75
|
+
# what you'll see in the simple spec,
|
76
|
+
# as the internal callback logic is called
|
77
|
+
# twice due to 2 items in the outer_numbers
|
78
|
+
# collection.
|
79
|
+
expect(result.counter).to eq(6)
|
80
|
+
expect(result.total).to eq(12)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'with failed or skipped context' do
|
85
|
+
class TestWithFailureCallback
|
86
|
+
extend LightService::Organizer
|
87
|
+
|
88
|
+
def self.call(context = {})
|
89
|
+
with(context).reduce(actions)
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.actions
|
93
|
+
[
|
94
|
+
TestDoubles::SetUpContextAction,
|
95
|
+
with_callback(TestDoubles::IterateCollectionAction,
|
96
|
+
[TestDoubles::IncrementCountAction,
|
97
|
+
TestDoubles::FailureAction])
|
98
|
+
]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'will not process the routine' do
|
103
|
+
result = TestWithFailureCallback.call
|
104
|
+
|
105
|
+
expect(result).to be_failure
|
106
|
+
expect(result.counter).to eq(1)
|
107
|
+
expect(result.total).to eq(0)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -3,16 +3,13 @@ require 'test_doubles'
|
|
3
3
|
|
4
4
|
describe LightService::Organizer::WithReducer do
|
5
5
|
let(:context) { LightService::Context.make }
|
6
|
-
let(:action1) {
|
7
|
-
let(:action2) {
|
6
|
+
let(:action1) { TestDoubles::NullAction }
|
7
|
+
let(:action2) { TestDoubles::NullAction.clone }
|
8
8
|
let(:actions) { [action1, action2] }
|
9
9
|
|
10
10
|
before { context.current_action = action2 }
|
11
11
|
|
12
12
|
it "reduces the provided actions" do
|
13
|
-
expect(action1).to receive(:execute).with(context).and_return(context)
|
14
|
-
expect(action2).to receive(:execute).with(context).and_return(context)
|
15
|
-
|
16
13
|
result = described_class.new.with(context).reduce(actions)
|
17
14
|
|
18
15
|
expect(result).to eq(context)
|
@@ -21,8 +18,6 @@ describe LightService::Organizer::WithReducer do
|
|
21
18
|
|
22
19
|
it "executes a handler around each action and continues reducing" do
|
23
20
|
expect(action1).to receive(:execute).with(context).and_return(context)
|
24
|
-
expect(TestDoubles::AroundEachNullHandler).to receive(:call)
|
25
|
-
.with(context).and_yield
|
26
21
|
|
27
22
|
result = described_class.new.with(context)
|
28
23
|
.around_each(TestDoubles::AroundEachNullHandler)
|
@@ -6,10 +6,7 @@ require_relative 'tax/provides_free_shipping_action'
|
|
6
6
|
|
7
7
|
describe CalculatesTax do
|
8
8
|
let(:order) { double('order') }
|
9
|
-
let(:ctx)
|
10
|
-
double('ctx', :keys => [:user],
|
11
|
-
:failure? => false, :skip_remaining? => false)
|
12
|
-
end
|
9
|
+
let(:ctx) { LightService::Context.make(:user => nil) }
|
13
10
|
|
14
11
|
it "calls the actions in order" do
|
15
12
|
allow(LightService::Context).to receive(:make)
|
data/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,22 @@
|
|
1
1
|
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
2
2
|
$LOAD_PATH << File.join(File.dirname(__FILE__))
|
3
3
|
|
4
|
+
if ENV['RUN_COVERAGE_REPORT']
|
5
|
+
require 'simplecov'
|
6
|
+
|
7
|
+
SimpleCov.start do
|
8
|
+
add_filter 'vendor/'
|
9
|
+
add_filter %r{^/spec/}
|
10
|
+
end
|
11
|
+
|
12
|
+
SimpleCov.minimum_coverage_by_file 90
|
13
|
+
end
|
14
|
+
|
4
15
|
require 'light-service'
|
5
16
|
require 'light-service/testing'
|
6
17
|
require 'ostruct'
|
7
18
|
require 'active_support/core_ext/string'
|
8
19
|
require 'pry'
|
20
|
+
require 'support'
|
9
21
|
|
10
22
|
I18n.enforce_available_locales = true
|
data/spec/support.rb
ADDED
data/spec/test_doubles.rb
CHANGED
@@ -30,26 +30,15 @@ module TestDoubles
|
|
30
30
|
executed(&:fail!)
|
31
31
|
end
|
32
32
|
|
33
|
-
class AddOneAction
|
34
|
-
extend LightService::Action
|
35
|
-
expects :number
|
36
|
-
promises :number
|
37
|
-
|
38
|
-
executed do |ctx|
|
39
|
-
ctx.number += 1
|
40
|
-
ctx.message = 'Added 1'
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
33
|
class AddTwoOrganizer
|
45
34
|
extend LightService::Organizer
|
46
35
|
def self.call(context)
|
47
|
-
with(context).reduce([
|
36
|
+
with(context).reduce([AddsOneAction, AddsOneAction])
|
48
37
|
end
|
49
38
|
end
|
50
39
|
|
51
40
|
class AroundEachNullHandler
|
52
|
-
def self.call(_action
|
41
|
+
def self.call(_action)
|
53
42
|
yield
|
54
43
|
end
|
55
44
|
end
|
@@ -149,13 +138,11 @@ module TestDoubles
|
|
149
138
|
executed do |context|
|
150
139
|
if context.milk == :very_hot
|
151
140
|
context.fail!("Can't make a latte from a milk that's very hot!")
|
152
|
-
next context
|
153
141
|
end
|
154
142
|
|
155
143
|
if context.milk == :super_hot
|
156
144
|
error_message = "Can't make a latte from a milk that's super hot!"
|
157
145
|
context.fail_with_rollback!(error_message)
|
158
|
-
next context
|
159
146
|
end
|
160
147
|
|
161
148
|
context[:latte] = "#{context.coffee} - with lots of #{context.milk}"
|
@@ -258,10 +245,9 @@ module TestDoubles
|
|
258
245
|
class AddsThreeAction
|
259
246
|
extend LightService::Action
|
260
247
|
expects :number
|
261
|
-
promises :product
|
262
248
|
|
263
249
|
executed do |context|
|
264
|
-
context.
|
250
|
+
context.number += 3
|
265
251
|
end
|
266
252
|
end
|
267
253
|
|
@@ -337,4 +323,85 @@ module TestDoubles
|
|
337
323
|
ctx.final_key = ctx.expected_key
|
338
324
|
end
|
339
325
|
end
|
326
|
+
|
327
|
+
class NullAction
|
328
|
+
extend LightService::Action
|
329
|
+
|
330
|
+
executed { |_ctx| }
|
331
|
+
end
|
332
|
+
|
333
|
+
class TestIterate
|
334
|
+
extend LightService::Organizer
|
335
|
+
|
336
|
+
def self.call(context)
|
337
|
+
with(context)
|
338
|
+
.reduce([iterate(:counters,
|
339
|
+
[TestDoubles::AddsOneAction])])
|
340
|
+
end
|
341
|
+
|
342
|
+
def self.call_single(context)
|
343
|
+
with(context)
|
344
|
+
.reduce([iterate(:counters,
|
345
|
+
TestDoubles::AddsOneAction)])
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
class TestWithCallback
|
350
|
+
extend LightService::Organizer
|
351
|
+
|
352
|
+
def self.call(context = {})
|
353
|
+
with(context).reduce(actions)
|
354
|
+
end
|
355
|
+
|
356
|
+
def self.actions
|
357
|
+
[
|
358
|
+
SetUpContextAction,
|
359
|
+
with_callback(IterateCollectionAction,
|
360
|
+
[IncrementCountAction,
|
361
|
+
AddToTotalAction])
|
362
|
+
]
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
class SetUpContextAction
|
367
|
+
extend LightService::Action
|
368
|
+
promises :numbers, :counter, :total
|
369
|
+
|
370
|
+
executed do |ctx|
|
371
|
+
ctx.numbers = [1, 2, 3]
|
372
|
+
ctx.counter = 0
|
373
|
+
ctx.total = 0
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
class IterateCollectionAction
|
378
|
+
extend LightService::Action
|
379
|
+
expects :numbers, :callback
|
380
|
+
promises :number
|
381
|
+
|
382
|
+
executed do |ctx|
|
383
|
+
ctx.numbers.each do |number|
|
384
|
+
ctx.number = number
|
385
|
+
ctx.callback.call(ctx)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
class IncrementCountAction
|
391
|
+
extend LightService::Action
|
392
|
+
expects :counter
|
393
|
+
|
394
|
+
executed do |ctx|
|
395
|
+
ctx.counter = ctx.counter + 1
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
class AddToTotalAction
|
400
|
+
extend LightService::Action
|
401
|
+
expects :number, :total
|
402
|
+
|
403
|
+
executed do |ctx|
|
404
|
+
ctx.total += ctx.number
|
405
|
+
end
|
406
|
+
end
|
340
407
|
end
|