light-service 0.8.4 → 0.9.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/README.md +25 -33
  4. data/RELEASES.md +3 -0
  5. data/lib/light-service.rb +7 -0
  6. data/lib/light-service/orchestrator.rb +21 -3
  7. data/lib/light-service/organizer.rb +15 -20
  8. data/lib/light-service/organizer/execute.rb +14 -0
  9. data/lib/light-service/organizer/iterate.rb +22 -0
  10. data/lib/light-service/organizer/reduce_if.rb +17 -0
  11. data/lib/light-service/organizer/reduce_until.rb +20 -0
  12. data/lib/light-service/organizer/scoped_reducable.rb +13 -0
  13. data/lib/light-service/organizer/verify_call_method_exists.rb +28 -0
  14. data/lib/light-service/organizer/with_callback.rb +26 -0
  15. data/lib/light-service/organizer/with_reducer.rb +14 -4
  16. data/lib/light-service/organizer/with_reducer_factory.rb +2 -0
  17. data/lib/light-service/version.rb +1 -1
  18. data/light-service.gemspec +1 -1
  19. data/resources/orchestrators_deprecated.svg +10 -0
  20. data/spec/acceptance/orchestrator/context_failure_and_skipping_spec.rb +6 -4
  21. data/spec/acceptance/orchestrator/execute_spec.rb +6 -4
  22. data/spec/acceptance/orchestrator/iterate_spec.rb +7 -5
  23. data/spec/acceptance/orchestrator/organizer_action_combination_spec.rb +13 -11
  24. data/spec/acceptance/orchestrator/reduce_if_spec.rb +7 -5
  25. data/spec/acceptance/orchestrator/reduce_until_spec.rb +6 -4
  26. data/spec/acceptance/orchestrator/with_callback_spec.rb +8 -6
  27. data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
  28. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +65 -0
  29. data/spec/acceptance/organizer/execute_spec.rb +46 -0
  30. data/spec/acceptance/organizer/iterate_spec.rb +51 -0
  31. data/spec/acceptance/organizer/reduce_if_spec.rb +51 -0
  32. data/spec/acceptance/organizer/reduce_until_spec.rb +43 -0
  33. data/spec/acceptance/organizer/with_callback_spec.rb +169 -0
  34. data/spec/organizer/with_reducer_spec.rb +2 -7
  35. data/spec/spec_helper.rb +12 -0
  36. data/spec/support.rb +9 -0
  37. data/spec/test_doubles.rb +7 -3
  38. metadata +28 -4
@@ -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::AddOneAction,
15
+ execute(->(ctx) { ctx.number += 1 }),
16
+ execute(->(ctx) { ctx[:something] = 'hello' }),
17
+ TestDoubles::AddOneAction
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,51 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe LightService::Organizer do
5
+ class TestIterate
6
+ extend LightService::Organizer
7
+
8
+ def self.call(context)
9
+ with(context)
10
+ .reduce([iterate(:numbers,
11
+ [TestDoubles::AddOneAction])])
12
+ end
13
+
14
+ def self.call_single(context)
15
+ with(context)
16
+ .reduce([iterate(:numbers,
17
+ TestDoubles::AddOneAction)])
18
+ end
19
+ end
20
+
21
+ let(:empty_context) { LightService::Context.make }
22
+
23
+ it 'reduces each item of a collection and singularizes the collection key' do
24
+ result = TestIterate.call(:numbers => [1, 2, 3, 4])
25
+
26
+ expect(result).to be_success
27
+ expect(result.number).to eq(5)
28
+ end
29
+
30
+ it 'accepts a single action or organizer' do
31
+ result = TestIterate.call_single(:numbers => [1, 2, 3, 4])
32
+
33
+ expect(result).to be_success
34
+ expect(result.number).to eq(5)
35
+ end
36
+
37
+ it 'will not iterate over a failed context' do
38
+ empty_context.fail!('Something bad happened')
39
+
40
+ result = TestIterate.call(empty_context)
41
+
42
+ expect(result).to be_failure
43
+ end
44
+
45
+ it 'does not iterate over a skipped context' do
46
+ empty_context.skip_remaining!('No more needed')
47
+
48
+ result = TestIterate.call(empty_context)
49
+ expect(result).to be_success
50
+ end
51
+ 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::AddOneAction,
15
+ reduce_if(->(ctx) { ctx.number == 1 },
16
+ TestDoubles::AddOneAction)
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::AddOneAction)
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,169 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe LightService::Organizer do
5
+ class TestWithCallback
6
+ extend LightService::Organizer
7
+
8
+ def self.call(context = {})
9
+ with(context).reduce(actions)
10
+ end
11
+
12
+ def self.actions
13
+ [
14
+ SetUpContextAction,
15
+ with_callback(IterateCollectionAction,
16
+ [IncrementCountAction,
17
+ AddToTotalAction])
18
+ ]
19
+ end
20
+ end
21
+
22
+ class SetUpContextAction
23
+ extend LightService::Action
24
+ promises :numbers, :counter, :total
25
+
26
+ executed do |ctx|
27
+ ctx.numbers = [1, 2, 3]
28
+ ctx.counter = 0
29
+ ctx.total = 0
30
+ end
31
+ end
32
+
33
+ class IterateCollectionAction
34
+ extend LightService::Action
35
+ expects :numbers, :callback
36
+ promises :number
37
+
38
+ executed do |ctx|
39
+ ctx.numbers.each do |number|
40
+ ctx.number = number
41
+ ctx.callback.call(ctx)
42
+ end
43
+ end
44
+ end
45
+
46
+ class IncrementCountAction
47
+ extend LightService::Action
48
+ expects :counter
49
+
50
+ executed do |ctx|
51
+ ctx.counter = ctx.counter + 1
52
+ end
53
+ end
54
+
55
+ class AddToTotalAction
56
+ extend LightService::Action
57
+ expects :number, :total
58
+
59
+ executed do |ctx|
60
+ ctx.total += ctx.number
61
+ end
62
+ end
63
+
64
+ describe 'a simple case with a single callback' do
65
+ it 'calls the actions defined with callback' do
66
+ result = TestWithCallback.call
67
+
68
+ expect(result.counter).to eq(3)
69
+ expect(result.total).to eq(6)
70
+ end
71
+ end
72
+
73
+ describe 'a more complex example with nested callbacks' do
74
+ class TestWithNestedCallback
75
+ extend LightService::Organizer
76
+
77
+ def self.call(context = {})
78
+ with(context).reduce(actions)
79
+ end
80
+
81
+ def self.actions
82
+ [
83
+ SetUpNestedContextAction,
84
+ with_callback(IterateOuterCollectionAction,
85
+ [IncrementOuterCountAction,
86
+ with_callback(IterateCollectionAction,
87
+ [IncrementCountAction,
88
+ AddToTotalAction])])
89
+ ]
90
+ end
91
+ end
92
+
93
+ class SetUpNestedContextAction
94
+ extend LightService::Action
95
+ promises :outer_numbers, :outer_counter,
96
+ :numbers, :counter, :total
97
+
98
+ executed do |ctx|
99
+ ctx.outer_numbers = [12, 17]
100
+ ctx.outer_counter = 0
101
+ ctx.numbers = [1, 2, 3]
102
+ ctx.counter = 0
103
+ ctx.total = 0
104
+ end
105
+ end
106
+
107
+ class IterateOuterCollectionAction
108
+ extend LightService::Action
109
+ expects :outer_numbers, :callback
110
+ promises :outer_number
111
+
112
+ executed do |ctx|
113
+ ctx.outer_numbers.each do |outer_number|
114
+ ctx.outer_number = outer_number
115
+ ctx.callback.call(ctx)
116
+ end
117
+ end
118
+ end
119
+
120
+ class IncrementOuterCountAction
121
+ extend LightService::Action
122
+ expects :outer_counter
123
+
124
+ executed do |ctx|
125
+ ctx.outer_counter = ctx.outer_counter + 1
126
+ end
127
+ end
128
+
129
+ it 'calls both the action and the nested callbacks' do
130
+ result = TestWithNestedCallback.call
131
+
132
+ expect(result.outer_counter).to eq(2)
133
+ # Counts and total are the duplicates of
134
+ # what you'll see in the simple spec,
135
+ # as the internal callback logic is called
136
+ # twice due to 2 items in the outer_numbers
137
+ # collection.
138
+ expect(result.counter).to eq(6)
139
+ expect(result.total).to eq(12)
140
+ end
141
+ end
142
+
143
+ describe 'with failed or skipped context' do
144
+ class TestWithFailureCallback
145
+ extend LightService::Organizer
146
+
147
+ def self.call(context = {})
148
+ with(context).reduce(actions)
149
+ end
150
+
151
+ def self.actions
152
+ [
153
+ SetUpContextAction,
154
+ with_callback(IterateCollectionAction,
155
+ [IncrementCountAction,
156
+ TestDoubles::FailureAction])
157
+ ]
158
+ end
159
+ end
160
+
161
+ it 'will not process the routine' do
162
+ result = TestWithFailureCallback.call
163
+
164
+ expect(result).to be_failure
165
+ expect(result.counter).to eq(1)
166
+ expect(result.total).to eq(0)
167
+ end
168
+ end
169
+ 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) { double(:action1) }
7
- let(:action2) { double(: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)
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
@@ -0,0 +1,9 @@
1
+ RSpec.shared_context 'expect orchestrator warning' do
2
+ before do
3
+ expect(ActiveSupport::Deprecation)
4
+ .to receive(:warn)
5
+ .with(/^`Orchestrator#/)
6
+ .at_least(:once)
7
+ end
8
+ end
9
+
data/spec/test_doubles.rb CHANGED
@@ -49,7 +49,7 @@ module TestDoubles
49
49
  end
50
50
 
51
51
  class AroundEachNullHandler
52
- def self.call(_action, _context)
52
+ def self.call(_action)
53
53
  yield
54
54
  end
55
55
  end
@@ -149,13 +149,11 @@ module TestDoubles
149
149
  executed do |context|
150
150
  if context.milk == :very_hot
151
151
  context.fail!("Can't make a latte from a milk that's very hot!")
152
- next context
153
152
  end
154
153
 
155
154
  if context.milk == :super_hot
156
155
  error_message = "Can't make a latte from a milk that's super hot!"
157
156
  context.fail_with_rollback!(error_message)
158
- next context
159
157
  end
160
158
 
161
159
  context[:latte] = "#{context.coffee} - with lots of #{context.milk}"
@@ -337,4 +335,10 @@ module TestDoubles
337
335
  ctx.final_key = ctx.expected_key
338
336
  end
339
337
  end
338
+
339
+ class NullAction
340
+ extend LightService::Action
341
+
342
+ executed { |_ctx| }
343
+ end
340
344
  end