light-service 0.13.0 → 0.17.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/.github/workflows/project-build.yml +28 -0
- data/.travis.yml +3 -9
- data/Appraisals +0 -4
- data/Gemfile +0 -2
- data/README.md +287 -51
- data/RELEASES.md +21 -2
- data/gemfiles/activesupport_5.gemfile +0 -1
- data/gemfiles/activesupport_6.gemfile +0 -1
- data/lib/generators/light_service/action_generator.rb +90 -0
- data/lib/generators/light_service/generator_utils.rb +45 -0
- data/lib/generators/light_service/organizer_generator.rb +66 -0
- data/lib/generators/light_service/templates/action_spec_template.erb +31 -0
- data/lib/generators/light_service/templates/action_template.erb +30 -0
- data/lib/generators/light_service/templates/organizer_spec_template.erb +20 -0
- data/lib/generators/light_service/templates/organizer_template.erb +22 -0
- data/lib/light-service/action.rb +61 -4
- data/lib/light-service/context/key_verifier.rb +18 -1
- data/lib/light-service/context.rb +5 -3
- data/lib/light-service/errors.rb +1 -0
- data/lib/light-service/organizer/reduce_if_else.rb +21 -0
- data/lib/light-service/organizer/with_reducer.rb +12 -7
- data/lib/light-service/organizer/with_reducer_factory.rb +1 -1
- data/lib/light-service/organizer/with_reducer_log_decorator.rb +3 -0
- data/lib/light-service/organizer.rb +16 -3
- data/lib/light-service/version.rb +1 -1
- data/lib/light-service.rb +1 -0
- data/light-service.gemspec +6 -1
- data/spec/acceptance/after_actions_spec.rb +17 -0
- data/spec/acceptance/around_each_spec.rb +15 -0
- data/spec/acceptance/log_from_organizer_spec.rb +1 -1
- data/spec/acceptance/organizer/add_to_context_spec.rb +27 -0
- data/spec/acceptance/organizer/execute_with_add_to_context_spec.rb +28 -0
- data/spec/acceptance/organizer/iterate_spec.rb +7 -0
- data/spec/acceptance/organizer/reduce_if_else_spec.rb +60 -0
- data/spec/acceptance/organizer/reduce_if_spec.rb +6 -0
- data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
- data/spec/action_optional_expected_keys_spec.rb +82 -0
- data/spec/action_spec.rb +8 -0
- data/spec/lib/generators/action_generator_advanced_spec.rb +43 -0
- data/spec/lib/generators/action_generator_simple_spec.rb +37 -0
- data/spec/lib/generators/full_generator_test_blobs.rb +193 -0
- data/spec/lib/generators/organizer_generator_advanced_spec.rb +37 -0
- data/spec/lib/generators/organizer_generator_simple_spec.rb +37 -0
- data/spec/organizer_spec.rb +5 -0
- data/spec/spec_helper.rb +5 -1
- data/spec/test_doubles.rb +37 -0
- metadata +87 -9
- data/gemfiles/activesupport_3.gemfile +0 -8
- data/gemfiles/activesupport_4.gemfile +0 -8
- data/resources/orchestrators_deprecated.svg +0 -10
@@ -111,7 +111,24 @@ module LightService
|
|
111
111
|
end
|
112
112
|
|
113
113
|
def reserved_keys
|
114
|
-
%i[message error_code current_action].freeze
|
114
|
+
%i[message error_code current_action organized_by].freeze
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class ReservedKeysViaOrganizerVerifier < ReservedKeysVerifier
|
119
|
+
def initialize(context_data)
|
120
|
+
@context = LightService::Context.make(context_data)
|
121
|
+
end
|
122
|
+
|
123
|
+
def violated_keys
|
124
|
+
context.keys.map(&:to_sym) & reserved_keys
|
125
|
+
end
|
126
|
+
|
127
|
+
def error_message
|
128
|
+
<<~ERR
|
129
|
+
reserved keys cannot be added to the context
|
130
|
+
reserved key: [#{format_keys(violated_keys)}]
|
131
|
+
ERR
|
115
132
|
end
|
116
133
|
end
|
117
134
|
end
|
@@ -8,7 +8,8 @@ module LightService
|
|
8
8
|
|
9
9
|
# rubocop:disable ClassLength
|
10
10
|
class Context < Hash
|
11
|
-
attr_accessor :message, :error_code, :current_action
|
11
|
+
attr_accessor :message, :error_code, :current_action, :around_actions,
|
12
|
+
:organized_by
|
12
13
|
|
13
14
|
def initialize(context = {},
|
14
15
|
outcome = Outcomes::SUCCESS,
|
@@ -18,6 +19,7 @@ module LightService
|
|
18
19
|
@message = message
|
19
20
|
@error_code = error_code
|
20
21
|
@skip_remaining = false
|
22
|
+
|
21
23
|
context.to_hash.each { |k, v| self[k] = v }
|
22
24
|
end
|
23
25
|
|
@@ -114,9 +116,9 @@ module LightService
|
|
114
116
|
end
|
115
117
|
|
116
118
|
def define_accessor_methods_for_keys(keys)
|
117
|
-
return if keys.
|
119
|
+
return if keys.blank?
|
118
120
|
|
119
|
-
keys.each do |key|
|
121
|
+
Array(keys).each do |key|
|
120
122
|
next if respond_to?(key.to_sym)
|
121
123
|
|
122
124
|
define_singleton_method(key.to_s) { fetch(key) }
|
data/lib/light-service/errors.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
module LightService
|
2
|
+
module Organizer
|
3
|
+
class ReduceIfElse
|
4
|
+
extend ScopedReducable
|
5
|
+
|
6
|
+
def self.run(organizer, condition_block, if_steps, else_steps)
|
7
|
+
lambda do |ctx|
|
8
|
+
return ctx if ctx.stop_processing?
|
9
|
+
|
10
|
+
ctx = if condition_block.call(ctx)
|
11
|
+
scoped_reduce(organizer, ctx, if_steps)
|
12
|
+
else
|
13
|
+
scoped_reduce(organizer, ctx, else_steps)
|
14
|
+
end
|
15
|
+
|
16
|
+
ctx
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,10 +1,16 @@
|
|
1
1
|
module LightService
|
2
2
|
module Organizer
|
3
3
|
class WithReducer
|
4
|
-
attr_reader
|
4
|
+
attr_reader :context
|
5
|
+
attr_accessor :organizer
|
6
|
+
|
7
|
+
def initialize(monitored_organizer = nil)
|
8
|
+
@organizer = monitored_organizer
|
9
|
+
end
|
5
10
|
|
6
11
|
def with(data = {})
|
7
12
|
@context = LightService::Context.make(data)
|
13
|
+
@context.organized_by = organizer
|
8
14
|
self
|
9
15
|
end
|
10
16
|
|
@@ -24,6 +30,7 @@ module LightService
|
|
24
30
|
def reduce(*actions)
|
25
31
|
raise "No action(s) were provided" if actions.empty?
|
26
32
|
|
33
|
+
@context.around_actions ||= around_each_handler
|
27
34
|
actions.flatten!
|
28
35
|
|
29
36
|
actions.each_with_object(context) do |action, current_context|
|
@@ -53,12 +60,10 @@ module LightService
|
|
53
60
|
private
|
54
61
|
|
55
62
|
def invoke_action(current_context, action)
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
action.execute(current_context)
|
61
|
-
end
|
63
|
+
if action.respond_to?(:call)
|
64
|
+
action.call(current_context)
|
65
|
+
else
|
66
|
+
action.execute(current_context)
|
62
67
|
end
|
63
68
|
end
|
64
69
|
|
@@ -41,6 +41,10 @@ module LightService
|
|
41
41
|
ReduceIf.run(self, condition_block, steps)
|
42
42
|
end
|
43
43
|
|
44
|
+
def reduce_if_else(condition_block, if_steps, else_steps)
|
45
|
+
ReduceIfElse.run(self, condition_block, if_steps, else_steps)
|
46
|
+
end
|
47
|
+
|
44
48
|
def reduce_until(condition_block, steps)
|
45
49
|
ReduceUntil.run(self, condition_block, steps)
|
46
50
|
end
|
@@ -65,9 +69,18 @@ module LightService
|
|
65
69
|
@logger
|
66
70
|
end
|
67
71
|
|
68
|
-
|
69
|
-
|
70
|
-
|
72
|
+
# Set the value as a key on the context hash
|
73
|
+
# and also create convenience accessors for the keys
|
74
|
+
def add_to_context(args)
|
75
|
+
Context::ReservedKeysViaOrganizerVerifier.new(args).verify
|
76
|
+
|
77
|
+
Hash(args).map do |key, value|
|
78
|
+
context_key = lambda do |ctx|
|
79
|
+
ctx[key.to_sym] = value
|
80
|
+
ctx.define_accessor_methods_for_keys(key)
|
81
|
+
end
|
82
|
+
|
83
|
+
execute(context_key)
|
71
84
|
end
|
72
85
|
end
|
73
86
|
|
data/lib/light-service.rb
CHANGED
@@ -13,6 +13,7 @@ require 'light-service/organizer/with_reducer'
|
|
13
13
|
require 'light-service/organizer/with_reducer_log_decorator'
|
14
14
|
require 'light-service/organizer/with_reducer_factory'
|
15
15
|
require 'light-service/organizer/reduce_if'
|
16
|
+
require 'light-service/organizer/reduce_if_else'
|
16
17
|
require 'light-service/organizer/reduce_until'
|
17
18
|
require 'light-service/organizer/iterate'
|
18
19
|
require 'light-service/organizer/execute'
|
data/light-service.gemspec
CHANGED
@@ -15,11 +15,16 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.name = "light-service"
|
16
16
|
gem.require_paths = ["lib"]
|
17
17
|
gem.version = LightService::VERSION
|
18
|
+
gem.required_ruby_version = '>= 2.5.0'
|
18
19
|
|
19
|
-
gem.add_runtime_dependency("activesupport", ">=
|
20
|
+
gem.add_runtime_dependency("activesupport", ">= 4.0.0")
|
20
21
|
|
22
|
+
gem.add_development_dependency("generator_spec", "~> 0.9.4")
|
23
|
+
gem.add_development_dependency("test-unit", "~> 3.0") # Needed for generator specs.
|
24
|
+
gem.add_development_dependency("appraisal", "~> 2.3")
|
21
25
|
gem.add_development_dependency("rspec", "~> 3.0")
|
22
26
|
gem.add_development_dependency("simplecov", "~> 0.17")
|
27
|
+
gem.add_development_dependency("codecov", "~> 0.1")
|
23
28
|
gem.add_development_dependency("rubocop", "~> 0.68.0")
|
24
29
|
gem.add_development_dependency("rubocop-performance", "~> 1.2.0")
|
25
30
|
gem.add_development_dependency("pry", "~> 0.12.2")
|
@@ -65,6 +65,23 @@ RSpec.describe 'Action after_actions' do
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
+
context 'with callbacks' do
|
69
|
+
it 'ensures the correct :current_action is set' do
|
70
|
+
TestDoubles::TestWithCallback.after_actions = [
|
71
|
+
lambda do |ctx|
|
72
|
+
if ctx.current_action == TestDoubles::IterateCollectionAction
|
73
|
+
ctx.total -= 1000
|
74
|
+
end
|
75
|
+
end
|
76
|
+
]
|
77
|
+
|
78
|
+
result = TestDoubles::TestWithCallback.call
|
79
|
+
|
80
|
+
expect(result.counter).to eq(3)
|
81
|
+
expect(result.total).to eq(-994)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
68
85
|
describe 'after_actions can be appended' do
|
69
86
|
it 'adds to the :_after_actions collection' do
|
70
87
|
TestDoubles::AdditionOrganizer.append_after_actions(
|
@@ -16,4 +16,19 @@ describe 'Executing arbitrary code around each action' do
|
|
16
16
|
}]
|
17
17
|
)
|
18
18
|
end
|
19
|
+
|
20
|
+
it 'logs data with nested actions' do
|
21
|
+
context = { :number => 1, :logger => TestDoubles::TestLogger.new }
|
22
|
+
|
23
|
+
result = TestDoubles::AroundEachWithReduceIfOrganizer.call(context)
|
24
|
+
|
25
|
+
expect(result.fetch(:number)).to eq(7)
|
26
|
+
expect(result[:logger].logs).to eq(
|
27
|
+
[
|
28
|
+
{ :action => TestDoubles::AddsOneAction, :before => 1, :after => 2 },
|
29
|
+
{ :action => TestDoubles::AddsTwoAction, :before => 2, :after => 4 },
|
30
|
+
{ :action => TestDoubles::AddsThreeAction, :before => 4, :after => 7 }
|
31
|
+
]
|
32
|
+
)
|
33
|
+
end
|
19
34
|
end
|
@@ -59,7 +59,7 @@ describe "Logs from organizer" do
|
|
59
59
|
expect(log_message).to include(organizer_log_message)
|
60
60
|
end
|
61
61
|
|
62
|
-
it "lists the keys in
|
62
|
+
it "lists the keys in context after the actions are executed" do
|
63
63
|
organizer_log_message = "[LightService] - keys in context: " \
|
64
64
|
":tea, :milk, :coffee, :milk_tea, :latte"
|
65
65
|
expect(log_message).to include(organizer_log_message)
|
@@ -20,6 +20,20 @@ RSpec.describe LightService::Organizer do
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
class TestAddToContextReservedWords
|
24
|
+
extend LightService::Organizer
|
25
|
+
|
26
|
+
def self.call(context = LightService::Context.make)
|
27
|
+
with(context).reduce(steps)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.steps
|
31
|
+
[
|
32
|
+
add_to_context(:message => "yo", "error_code" => "00P5")
|
33
|
+
]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
23
37
|
it 'adds items to the context on the fly' do
|
24
38
|
result = TestAddToContext.call
|
25
39
|
|
@@ -27,4 +41,17 @@ RSpec.describe LightService::Organizer do
|
|
27
41
|
expect(result.number).to eq(1)
|
28
42
|
expect(result[:something]).to eq('hello')
|
29
43
|
end
|
44
|
+
|
45
|
+
it 'adds items to the context as accessors' do
|
46
|
+
result = TestAddToContext.call
|
47
|
+
|
48
|
+
expect(result).to be_success
|
49
|
+
expect(result.something).to eq('hello')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "will not add items as accessors when they are reserved" do
|
53
|
+
expect { TestAddToContextReservedWords.call }.to \
|
54
|
+
raise_error(LightService::ReservedKeysInContextError)
|
55
|
+
.with_message(/:message, :error_code/)
|
56
|
+
end
|
30
57
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
RSpec.describe LightService::Organizer do
|
5
|
+
class TestExecuteWithAddToContext
|
6
|
+
extend LightService::Organizer
|
7
|
+
|
8
|
+
def self.call
|
9
|
+
with.reduce(steps)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.steps
|
13
|
+
[
|
14
|
+
add_to_context(:greeting => "hello"),
|
15
|
+
execute(->(ctx) { ctx.greeting.upcase! })
|
16
|
+
]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when using context values created by add_to_context" do
|
21
|
+
it "is expected to reference them as accessors" do
|
22
|
+
result = TestExecuteWithAddToContext.call
|
23
|
+
|
24
|
+
expect(result).to be_a_success
|
25
|
+
expect(result.greeting).to eq "HELLO"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -20,6 +20,13 @@ RSpec.describe LightService::Organizer do
|
|
20
20
|
expect(result.number).to eq(5)
|
21
21
|
end
|
22
22
|
|
23
|
+
it "knows that it's being iterated from within an organizer" do
|
24
|
+
result = TestDoubles::TestIterate.call(:number => 1,
|
25
|
+
:counters => [1, 2, 3, 4])
|
26
|
+
|
27
|
+
expect(result.organized_by).to eq TestDoubles::TestIterate
|
28
|
+
end
|
29
|
+
|
23
30
|
it 'will not iterate over a failed context' do
|
24
31
|
empty_context.fail!('Something bad happened')
|
25
32
|
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_doubles'
|
3
|
+
|
4
|
+
RSpec.describe LightService::Organizer do
|
5
|
+
class TestReduceIfElse
|
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_else(
|
16
|
+
->(ctx) { ctx.number == 1 },
|
17
|
+
[TestDoubles::AddsOneAction],
|
18
|
+
[TestDoubles::AddsTwoAction]
|
19
|
+
)
|
20
|
+
]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:empty_context) { LightService::Context.make }
|
25
|
+
|
26
|
+
it 'reduces the if_steps if the condition is true' do
|
27
|
+
result = TestReduceIfElse.call(:number => 0)
|
28
|
+
|
29
|
+
expect(result).to be_success
|
30
|
+
expect(result[:number]).to eq(2)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'reduces the else_steps if the condition is false' do
|
34
|
+
result = TestReduceIfElse.call(:number => 2)
|
35
|
+
|
36
|
+
expect(result).to be_success
|
37
|
+
expect(result[:number]).to eq(5)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'will not reduce over a failed context' do
|
41
|
+
empty_context.fail!('Something bad happened')
|
42
|
+
|
43
|
+
result = TestReduceIfElse.call(empty_context)
|
44
|
+
|
45
|
+
expect(result).to be_failure
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'does not reduce over a skipped context' do
|
49
|
+
empty_context.skip_remaining!('No more needed')
|
50
|
+
|
51
|
+
result = TestReduceIfElse.call(empty_context)
|
52
|
+
expect(result).to be_success
|
53
|
+
end
|
54
|
+
|
55
|
+
it "knows that it's being conditionally reduced from within an organizer" do
|
56
|
+
result = TestReduceIfElse.call(:number => 2)
|
57
|
+
|
58
|
+
expect(result.organized_by).to eq TestReduceIfElse
|
59
|
+
end
|
60
|
+
end
|
@@ -49,6 +49,12 @@ RSpec.describe LightService::Organizer do
|
|
49
49
|
expect(result).to be_success
|
50
50
|
end
|
51
51
|
|
52
|
+
it "knows that it's being conditionally reduced from within an organizer" do
|
53
|
+
result = TestReduceIf.call(:number => 2)
|
54
|
+
|
55
|
+
expect(result.organized_by).to eq TestReduceIf
|
56
|
+
end
|
57
|
+
|
52
58
|
it 'skips actions within in its own scope' do
|
53
59
|
org = Class.new do
|
54
60
|
extend LightService::Organizer
|
@@ -40,4 +40,10 @@ RSpec.describe LightService::Organizer do
|
|
40
40
|
result = TestReduceUntil.call(empty_context)
|
41
41
|
expect(result).to be_success
|
42
42
|
end
|
43
|
+
|
44
|
+
it "is expected to know its organizer when reducing until a condition" do
|
45
|
+
result = TestReduceUntil.call(:number => 1)
|
46
|
+
|
47
|
+
expect(result.organized_by).to eq TestReduceUntil
|
48
|
+
end
|
43
49
|
end
|