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
@@ -17,8 +17,19 @@ module LightService
17
17
  # In case this module is included
18
18
  module ClassMethods
19
19
  def with(data = {})
20
- VerifyCallMethodExists.call(self, caller(1..1).first)
20
+ VerifyCallMethodExists.run(self, caller(1..1).first)
21
21
  data[:_aliases] = @aliases if @aliases
22
+
23
+ if @before_actions
24
+ data[:_before_actions] = @before_actions.dup
25
+ @before_actions = nil
26
+ end
27
+
28
+ if @after_actions
29
+ data[:_after_actions] = @after_actions.dup
30
+ @after_actions = nil
31
+ end
32
+
22
33
  WithReducerFactory.make(self).with(data)
23
34
  end
24
35
 
@@ -26,29 +37,24 @@ module LightService
26
37
  with({}).reduce(actions)
27
38
  end
28
39
 
29
- # We need to make sure existing users will
30
- # use `call` method name going forward.
31
- # This should be removed eventually.
32
- class VerifyCallMethodExists
33
- def self.call(klass, first_caller = '')
34
- invoker_method = caller_method(first_caller)
35
- return if invoker_method == 'call'
40
+ def reduce_if(condition_block, steps)
41
+ ReduceIf.run(self, condition_block, steps)
42
+ end
36
43
 
37
- call_method_exists = klass.methods.include?(:call)
38
- return if call_method_exists
44
+ def reduce_until(condition_block, steps)
45
+ ReduceUntil.run(self, condition_block, steps)
46
+ end
39
47
 
40
- warning_msg = "The <#{klass.name}> class is an organizer, " \
41
- "its entry method (the one that calls with & reduce) " \
42
- "should be named `call`. " \
43
- "Please use #{klass}.call going forward."
44
- ActiveSupport::Deprecation.warn(warning_msg)
45
- end
48
+ def iterate(collection_key, steps)
49
+ Iterate.run(self, collection_key, steps)
50
+ end
46
51
 
47
- def self.caller_method(first_caller)
48
- return nil unless first_caller =~ /`(.*)'/
52
+ def execute(code_block)
53
+ Execute.run(code_block)
54
+ end
49
55
 
50
- Regexp.last_match[1]
51
- end
56
+ def with_callback(action, steps)
57
+ WithCallback.run(self, action, steps)
52
58
  end
53
59
  end
54
60
 
@@ -56,6 +62,22 @@ module LightService
56
62
  def aliases(key_hash)
57
63
  @aliases = key_hash
58
64
  end
65
+
66
+ def before_actions(*logic)
67
+ self.before_actions = logic
68
+ end
69
+
70
+ def before_actions=(logic)
71
+ @before_actions = [logic].flatten
72
+ end
73
+
74
+ def after_actions(*logic)
75
+ self.after_actions = logic
76
+ end
77
+
78
+ def after_actions=(logic)
79
+ @after_actions = [logic].flatten
80
+ end
59
81
  end
60
82
  end
61
83
  end
@@ -0,0 +1,14 @@
1
+ module LightService
2
+ module Organizer
3
+ class Execute
4
+ def self.run(code_block)
5
+ lambda do |ctx|
6
+ return ctx if ctx.stop_processing?
7
+
8
+ code_block.call(ctx)
9
+ ctx
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ module LightService
2
+ module Organizer
3
+ class Iterate
4
+ extend ScopedReducable
5
+
6
+ def self.run(organizer, collection_key, steps)
7
+ lambda do |ctx|
8
+ return ctx if ctx.stop_processing?
9
+
10
+ collection = ctx[collection_key]
11
+ item_key = collection_key.to_s.singularize.to_sym
12
+ collection.each do |item|
13
+ ctx[item_key] = item
14
+ ctx = scoped_reduce(organizer, ctx, steps)
15
+ end
16
+
17
+ ctx
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module LightService
2
+ module Organizer
3
+ class ReduceIf
4
+ extend ScopedReducable
5
+
6
+ def self.run(organizer, condition_block, steps)
7
+ lambda do |ctx|
8
+ return ctx if ctx.stop_processing?
9
+
10
+ ctx = scoped_reduce(organizer, ctx, steps) \
11
+ if condition_block.call(ctx)
12
+ ctx
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module LightService
2
+ module Organizer
3
+ class ReduceUntil
4
+ extend ScopedReducable
5
+
6
+ def self.run(organizer, condition_block, steps)
7
+ lambda do |ctx|
8
+ return ctx if ctx.stop_processing?
9
+
10
+ loop do
11
+ ctx = scoped_reduce(organizer, ctx, steps)
12
+ break if condition_block.call(ctx) || ctx.failure?
13
+ end
14
+
15
+ ctx
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,13 @@
1
+ module LightService
2
+ module Organizer
3
+ module ScopedReducable
4
+ def scoped_reduce(organizer, ctx, steps)
5
+ ctx.reset_skip_remaining! unless ctx.failure?
6
+ ctx = organizer.with(ctx).reduce([steps])
7
+ ctx.reset_skip_remaining! unless ctx.failure?
8
+
9
+ ctx
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ module LightService
2
+ module Organizer
3
+ # We need to make sure existing users will
4
+ # use `call` method name going forward.
5
+ # This should be removed eventually.
6
+ class VerifyCallMethodExists
7
+ def self.run(klass, first_caller = '')
8
+ invoker_method = caller_method(first_caller)
9
+ return if invoker_method == 'call'
10
+
11
+ call_method_exists = klass.methods.include?(:call)
12
+ return if call_method_exists
13
+
14
+ warning_msg = "The <#{klass.name}> class is an organizer, " \
15
+ "its entry method (the one that calls with & reduce) " \
16
+ "should be named `call`. " \
17
+ "Please use #{klass}.call going forward."
18
+ ActiveSupport::Deprecation.warn(warning_msg)
19
+ end
20
+
21
+ def self.caller_method(first_caller)
22
+ return nil unless first_caller =~ /`(.*)'/
23
+
24
+ Regexp.last_match[1]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ module LightService
2
+ module Organizer
3
+ class WithCallback
4
+ extend ScopedReducable
5
+
6
+ def self.run(organizer, action, steps)
7
+ lambda do |ctx|
8
+ return ctx if ctx.stop_processing?
9
+
10
+ # This will only allow 2 level deep nesting of callbacks
11
+ previous_callback = ctx[:callback]
12
+
13
+ ctx[:callback] = lambda do |context|
14
+ ctx = scoped_reduce(organizer, context, steps)
15
+ ctx
16
+ end
17
+
18
+ ctx = action.execute(ctx)
19
+ ctx[:callback] = previous_callback
20
+
21
+ ctx
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,7 +1,7 @@
1
1
  module LightService
2
2
  module Organizer
3
3
  class WithReducer
4
- attr_reader :context, :around_each_handler
4
+ attr_reader :context
5
5
 
6
6
  def with(data = {})
7
7
  @context = LightService::Context.make(data)
@@ -13,6 +13,14 @@ module LightService
13
13
  self
14
14
  end
15
15
 
16
+ def around_each_handler
17
+ @around_each_handler ||= Class.new do
18
+ def self.call(_context)
19
+ yield
20
+ end
21
+ end
22
+ end
23
+
16
24
  def reduce(*actions)
17
25
  raise "No action(s) were provided" if actions.empty?
18
26
  actions.flatten!
@@ -46,10 +54,12 @@ module LightService
46
54
  private
47
55
 
48
56
  def invoke_action(current_context, action)
49
- return action.execute(current_context) unless around_each_handler
50
-
51
57
  around_each_handler.call(current_context) do
52
- action.execute(current_context)
58
+ if action.respond_to?(:call)
59
+ action.call(current_context)
60
+ else
61
+ action.execute(current_context)
62
+ end
53
63
  end
54
64
  end
55
65
 
@@ -3,7 +3,9 @@ module LightService
3
3
  class WithReducerFactory
4
4
  def self.make(monitored_organizer)
5
5
  if LightService::Configuration.logger.nil?
6
+ # :nocov:
6
7
  WithReducer.new
8
+ # :nocov:
7
9
  else
8
10
  WithReducerLogDecorator.new(monitored_organizer, WithReducer.new)
9
11
  end
@@ -1,3 +1,3 @@
1
1
  module LightService
2
- VERSION = "0.8.4".freeze
2
+ VERSION = "0.10.0".freeze
3
3
  end
@@ -19,7 +19,7 @@ Gem::Specification.new do |gem|
19
19
  gem.add_dependency("activesupport", ">= 3.0")
20
20
 
21
21
  gem.add_development_dependency("rspec", "~> 3.0")
22
- gem.add_development_dependency("simplecov", "~> 0.14.1")
23
- gem.add_development_dependency("rubocop", "~> 0.51")
22
+ gem.add_development_dependency("simplecov", "~> 0.16.1")
23
+ gem.add_development_dependency("rubocop", "~> 0.53")
24
24
  gem.add_development_dependency("pry", "~> 0.10")
25
25
  end
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <svg version="1.1"
3
+ xmlns="http://www.w3.org/2000/svg"
4
+ xmlns:xlink="http://www.w3.org/1999/xlink"
5
+ width="350" height="20"
6
+ >
7
+ <text font-size="20" x="0" y="20" font-family="Verdana">
8
+ <tspan fill="red">Orchestrators are DEPRECATED!</tspan>
9
+ </text>
10
+ </svg>
@@ -1,10 +1,10 @@
1
1
  require 'spec_helper'
2
2
  require 'test_doubles'
3
3
 
4
- describe TestDoubles::AdditionOrganizer do
5
- it "Adds 1, 2 and 3 to the initial value of 1" do
4
+ RSpec.describe TestDoubles::AdditionOrganizer do
5
+ it 'Adds 1, 2 and 3 to the initial value of 1' do
6
6
  result = TestDoubles::AdditionOrganizer.call(1)
7
- number = result.fetch(:product)
7
+ number = result.fetch(:number)
8
8
 
9
9
  expect(number).to eq(7)
10
10
  end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe 'Action after_actions' do
5
+ describe 'works with simple organizers - from outside' do
6
+ it 'can be used to inject code block before each action' do
7
+ TestDoubles::AdditionOrganizer.after_actions =
8
+ lambda do |ctx|
9
+ ctx.number -= 2 if ctx.current_action == TestDoubles::AddsThreeAction
10
+ end
11
+
12
+ result = TestDoubles::AdditionOrganizer.call(0)
13
+
14
+ expect(result.fetch(:number)).to eq(4)
15
+ end
16
+
17
+ it 'works with iterator' do
18
+ TestDoubles::TestIterate.after_actions = [
19
+ lambda do |ctx|
20
+ ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction
21
+ end
22
+ ]
23
+
24
+ result = TestDoubles::TestIterate.call(:number => 0,
25
+ :counters => [1, 2, 3, 4])
26
+
27
+ expect(result).to be_success
28
+ expect(result.number).to eq(-4)
29
+ end
30
+ end
31
+
32
+ describe 'can be added to organizers declaratively' do
33
+ module AfterActions
34
+ class AdditionOrganizer
35
+ extend LightService::Organizer
36
+ after_actions (lambda do |ctx|
37
+ if ctx.current_action == TestDoubles::AddsOneAction
38
+ ctx.number -= 2
39
+ end
40
+ end),
41
+ (lambda do |ctx|
42
+ if ctx.current_action == TestDoubles::AddsThreeAction
43
+ ctx.number -= 3
44
+ end
45
+ end)
46
+
47
+ def self.call(number)
48
+ with(:number => number).reduce(actions)
49
+ end
50
+
51
+ def self.actions
52
+ [
53
+ TestDoubles::AddsOneAction,
54
+ TestDoubles::AddsTwoAction,
55
+ TestDoubles::AddsThreeAction
56
+ ]
57
+ end
58
+ end
59
+ end
60
+
61
+ it 'accepts after_actions hook lambdas from organizer' do
62
+ result = AfterActions::AdditionOrganizer.call(0)
63
+
64
+ expect(result.fetch(:number)).to eq(1)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe 'Action before_actions' do
5
+ describe 'works with simple organizers - from outside' do
6
+ it 'can be used to inject code block before each action' do
7
+ TestDoubles::AdditionOrganizer.before_actions =
8
+ lambda do |ctx|
9
+ ctx.number -= 2 if ctx.current_action == TestDoubles::AddsThreeAction
10
+ end
11
+
12
+ result = TestDoubles::AdditionOrganizer.call(0)
13
+
14
+ expect(result.fetch(:number)).to eq(4)
15
+ end
16
+
17
+ it 'works with iterator' do
18
+ TestDoubles::TestIterate.before_actions = [
19
+ lambda do |ctx|
20
+ ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction
21
+ end
22
+ ]
23
+
24
+ result = TestDoubles::TestIterate.call(:number => 0,
25
+ :counters => [1, 2, 3, 4])
26
+
27
+ expect(result).to be_success
28
+ expect(result.number).to eq(-4)
29
+ end
30
+ end
31
+
32
+ describe 'can be added to organizers declaratively' do
33
+ module BeforeActions
34
+ class AdditionOrganizer
35
+ extend LightService::Organizer
36
+ before_actions (lambda do |ctx|
37
+ if ctx.current_action == TestDoubles::AddsOneAction
38
+ ctx.number -= 2
39
+ end
40
+ end),
41
+ (lambda do |ctx|
42
+ if ctx.current_action == TestDoubles::AddsThreeAction
43
+ ctx.number -= 3
44
+ end
45
+ end)
46
+
47
+ def self.call(number)
48
+ with(:number => number).reduce(actions)
49
+ end
50
+
51
+ def self.actions
52
+ [
53
+ TestDoubles::AddsOneAction,
54
+ TestDoubles::AddsTwoAction,
55
+ TestDoubles::AddsThreeAction
56
+ ]
57
+ end
58
+ end
59
+ end
60
+
61
+ it 'accepts before_actions hook lambdas from organizer' do
62
+ result = BeforeActions::AdditionOrganizer.call(0)
63
+
64
+ expect(result.fetch(:number)).to eq(1)
65
+ end
66
+ end
67
+
68
+ describe 'works with callbacks' do
69
+ it 'can interact with actions from the outside' do
70
+ TestDoubles::TestWithCallback.before_actions = [
71
+ lambda do |ctx|
72
+ if ctx.current_action == TestDoubles::AddToTotalAction
73
+ ctx.total -= 1000
74
+ end
75
+ end
76
+ ]
77
+ result = TestDoubles::TestWithCallback.call
78
+
79
+ expect(result.counter).to eq(3)
80
+ expect(result.total).to eq(-2994)
81
+ end
82
+ end
83
+
84
+ describe 'can halt all execution with a raised error' do
85
+ it 'does not call the rest of the callback steps' do
86
+ class SkipContextError < StandardError
87
+ attr_reader :ctx
88
+
89
+ def initialize(msg, ctx)
90
+ @ctx = ctx
91
+ super(msg)
92
+ end
93
+ end
94
+ TestDoubles::TestWithCallback.before_actions = [
95
+ lambda do |ctx|
96
+ if ctx.current_action == TestDoubles::IncrementCountAction
97
+ ctx.total -= 1000
98
+ raise SkipContextError.new("stop context now", ctx)
99
+ end
100
+ end
101
+ ]
102
+ begin
103
+ TestDoubles::TestWithCallback.call
104
+ rescue SkipContextError => e
105
+ expect(e.ctx).not_to be_empty
106
+ end
107
+ end
108
+ end
109
+ end