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
@@ -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.
|
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
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
38
|
-
|
44
|
+
def reduce_until(condition_block, steps)
|
45
|
+
ReduceUntil.run(self, condition_block, steps)
|
46
|
+
end
|
39
47
|
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
48
|
-
|
52
|
+
def execute(code_block)
|
53
|
+
Execute.run(code_block)
|
54
|
+
end
|
49
55
|
|
50
|
-
|
51
|
-
|
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,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
|
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.
|
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
|
|
data/light-service.gemspec
CHANGED
@@ -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.
|
23
|
-
gem.add_development_dependency("rubocop", "~> 0.
|
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
|
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(:
|
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
|