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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -2
  3. data/README.md +149 -52
  4. data/RELEASES.md +6 -0
  5. data/gemfiles/activesupport_3.gemfile +1 -1
  6. data/gemfiles/activesupport_4.gemfile +1 -1
  7. data/gemfiles/activesupport_5.gemfile +1 -1
  8. data/lib/light-service.rb +7 -0
  9. data/lib/light-service/action.rb +22 -2
  10. data/lib/light-service/orchestrator.rb +21 -3
  11. data/lib/light-service/organizer.rb +42 -20
  12. data/lib/light-service/organizer/execute.rb +14 -0
  13. data/lib/light-service/organizer/iterate.rb +22 -0
  14. data/lib/light-service/organizer/reduce_if.rb +17 -0
  15. data/lib/light-service/organizer/reduce_until.rb +20 -0
  16. data/lib/light-service/organizer/scoped_reducable.rb +13 -0
  17. data/lib/light-service/organizer/verify_call_method_exists.rb +28 -0
  18. data/lib/light-service/organizer/with_callback.rb +26 -0
  19. data/lib/light-service/organizer/with_reducer.rb +14 -4
  20. data/lib/light-service/organizer/with_reducer_factory.rb +2 -0
  21. data/lib/light-service/version.rb +1 -1
  22. data/light-service.gemspec +2 -2
  23. data/resources/orchestrators_deprecated.svg +10 -0
  24. data/spec/acceptance/add_numbers_spec.rb +3 -3
  25. data/spec/acceptance/after_actions_spec.rb +67 -0
  26. data/spec/acceptance/before_actions_spec.rb +109 -0
  27. data/spec/acceptance/orchestrator/context_failure_and_skipping_spec.rb +12 -10
  28. data/spec/acceptance/orchestrator/execute_spec.rb +8 -6
  29. data/spec/acceptance/orchestrator/iterate_spec.rb +9 -7
  30. data/spec/acceptance/orchestrator/organizer_action_combination_spec.rb +13 -11
  31. data/spec/acceptance/orchestrator/reduce_if_spec.rb +9 -7
  32. data/spec/acceptance/orchestrator/reduce_until_spec.rb +7 -5
  33. data/spec/acceptance/orchestrator/with_callback_spec.rb +8 -6
  34. data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
  35. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +65 -0
  36. data/spec/acceptance/organizer/execute_spec.rb +46 -0
  37. data/spec/acceptance/organizer/iterate_spec.rb +37 -0
  38. data/spec/acceptance/organizer/reduce_if_spec.rb +51 -0
  39. data/spec/acceptance/organizer/reduce_until_spec.rb +43 -0
  40. data/spec/acceptance/organizer/with_callback_spec.rb +110 -0
  41. data/spec/organizer/with_reducer_spec.rb +2 -7
  42. data/spec/sample/calculates_tax_spec.rb +1 -4
  43. data/spec/spec_helper.rb +12 -0
  44. data/spec/support.rb +9 -0
  45. data/spec/test_doubles.rb +84 -17
  46. 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) { 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)
@@ -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) do
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)
@@ -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
@@ -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
+
@@ -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([AddOneAction, AddOneAction])
36
+ with(context).reduce([AddsOneAction, AddsOneAction])
48
37
  end
49
38
  end
50
39
 
51
40
  class AroundEachNullHandler
52
- def self.call(_action, _context)
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.product = context.number + 3
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