light-service 0.11.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/project-build.yml +28 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +6 -0
  5. data/.travis.yml +7 -11
  6. data/Appraisals +4 -4
  7. data/Gemfile +0 -2
  8. data/README.md +257 -42
  9. data/RELEASES.md +21 -0
  10. data/gemfiles/activesupport_5.gemfile +0 -1
  11. data/gemfiles/{activesupport_3.gemfile → activesupport_6.gemfile} +1 -2
  12. data/lib/generators/light_service/action_generator.rb +90 -0
  13. data/lib/generators/light_service/generator_utils.rb +45 -0
  14. data/lib/generators/light_service/organizer_generator.rb +66 -0
  15. data/lib/generators/light_service/templates/action_spec_template.erb +31 -0
  16. data/lib/generators/light_service/templates/action_template.erb +30 -0
  17. data/lib/generators/light_service/templates/organizer_spec_template.erb +20 -0
  18. data/lib/generators/light_service/templates/organizer_template.erb +22 -0
  19. data/lib/light-service.rb +1 -0
  20. data/lib/light-service/action.rb +3 -0
  21. data/lib/light-service/context.rb +8 -4
  22. data/lib/light-service/context/key_verifier.rb +18 -1
  23. data/lib/light-service/localization_adapter.rb +1 -1
  24. data/lib/light-service/organizer.rb +27 -0
  25. data/lib/light-service/organizer/with_reducer.rb +8 -1
  26. data/lib/light-service/organizer/with_reducer_factory.rb +11 -7
  27. data/lib/light-service/organizer/with_reducer_log_decorator.rb +5 -2
  28. data/lib/light-service/version.rb +1 -1
  29. data/light-service.gemspec +10 -4
  30. data/spec/acceptance/after_actions_spec.rb +17 -0
  31. data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
  32. data/spec/acceptance/fail_spec.rb +42 -16
  33. data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
  34. data/spec/acceptance/organizer/add_to_context_spec.rb +57 -0
  35. data/spec/acceptance/organizer/execute_spec.rb +1 -1
  36. data/spec/acceptance/organizer/execute_with_add_to_context_spec.rb +28 -0
  37. data/spec/acceptance/organizer/iterate_spec.rb +7 -0
  38. data/spec/acceptance/organizer/reduce_if_spec.rb +38 -0
  39. data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
  40. data/spec/action_spec.rb +8 -0
  41. data/spec/lib/generators/action_generator_advanced_spec.rb +43 -0
  42. data/spec/lib/generators/action_generator_simple_spec.rb +37 -0
  43. data/spec/lib/generators/full_generator_test_blobs.rb +193 -0
  44. data/spec/lib/generators/organizer_generator_advanced_spec.rb +37 -0
  45. data/spec/lib/generators/organizer_generator_simple_spec.rb +37 -0
  46. data/spec/organizer_spec.rb +21 -0
  47. data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
  48. data/spec/spec_helper.rb +7 -2
  49. data/spec/test_doubles.rb +47 -0
  50. metadata +111 -21
  51. data/gemfiles/activesupport_3.gemfile.lock +0 -76
  52. data/gemfiles/activesupport_4.gemfile +0 -8
  53. data/gemfiles/activesupport_4.gemfile.lock +0 -82
  54. data/gemfiles/activesupport_5.gemfile.lock +0 -82
  55. data/resources/orchestrators_deprecated.svg +0 -10
@@ -34,7 +34,7 @@ module LightService
34
34
  scope = i18n_scope_from_class(action_class, type)
35
35
  options[:scope] = scope
36
36
 
37
- I18n.t(key, options)
37
+ I18n.t(key, **options)
38
38
  end
39
39
 
40
40
  def i18n_scope_from_class(action_class, type)
@@ -56,6 +56,33 @@ module LightService
56
56
  def with_callback(action, steps)
57
57
  WithCallback.run(self, action, steps)
58
58
  end
59
+
60
+ def log_with(logger)
61
+ @logger = logger
62
+ end
63
+
64
+ def logger
65
+ @logger
66
+ end
67
+
68
+ # Set the value as a key on the context hash
69
+ # and also create convenience accessors for the keys
70
+ def add_to_context(args)
71
+ Context::ReservedKeysViaOrganizerVerifier.new(args).verify
72
+
73
+ Hash(args).map do |key, value|
74
+ context_key = lambda do |ctx|
75
+ ctx[key.to_sym] = value
76
+ ctx.define_accessor_methods_for_keys(key)
77
+ end
78
+
79
+ execute(context_key)
80
+ end
81
+ end
82
+
83
+ def add_aliases(args)
84
+ execute(->(ctx) { ctx.assign_aliases(ctx.aliases.merge(args)) })
85
+ end
59
86
  end
60
87
 
61
88
  module Macros
@@ -1,10 +1,16 @@
1
1
  module LightService
2
2
  module Organizer
3
3
  class WithReducer
4
- attr_reader :context
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
 
@@ -23,6 +29,7 @@ module LightService
23
29
 
24
30
  def reduce(*actions)
25
31
  raise "No action(s) were provided" if actions.empty?
32
+
26
33
  actions.flatten!
27
34
 
28
35
  actions.each_with_object(context) do |action, current_context|
@@ -2,13 +2,17 @@ module LightService
2
2
  module Organizer
3
3
  class WithReducerFactory
4
4
  def self.make(monitored_organizer)
5
- if LightService::Configuration.logger.nil?
6
- # :nocov:
7
- WithReducer.new
8
- # :nocov:
9
- else
10
- WithReducerLogDecorator.new(monitored_organizer, WithReducer.new)
11
- end
5
+ logger = monitored_organizer.logger ||
6
+ LightService::Configuration.logger
7
+ decorated = WithReducer.new(monitored_organizer)
8
+
9
+ return decorated if logger.nil?
10
+
11
+ WithReducerLogDecorator.new(
12
+ monitored_organizer,
13
+ :decorated => decorated,
14
+ :logger => logger
15
+ )
12
16
  end
13
17
  end
14
18
  end
@@ -5,10 +5,13 @@ module LightService
5
5
 
6
6
  alias logged? logged
7
7
 
8
- def initialize(organizer, decorated = WithReducer.new)
8
+ def initialize(organizer, decorated: WithReducer.new, logger:)
9
9
  @decorated = decorated
10
10
  @organizer = organizer
11
- @logger = LightService::Configuration.logger
11
+
12
+ decorated.organizer = organizer
13
+
14
+ @logger = logger
12
15
  @logged = false
13
16
  end
14
17
 
@@ -1,3 +1,3 @@
1
1
  module LightService
2
- VERSION = "0.11.0".freeze
2
+ VERSION = "0.16.0".freeze
3
3
  end
@@ -15,11 +15,17 @@ 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_dependency("activesupport", ">= 3.0")
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
- gem.add_development_dependency("simplecov", "~> 0.16.1")
23
- gem.add_development_dependency("rubocop", "~> 0.53")
24
- gem.add_development_dependency("pry", "~> 0.10")
26
+ gem.add_development_dependency("simplecov", "~> 0.17")
27
+ gem.add_development_dependency("codecov", "~> 0.1")
28
+ gem.add_development_dependency("rubocop", "~> 0.68.0")
29
+ gem.add_development_dependency("rubocop-performance", "~> 1.2.0")
30
+ gem.add_development_dependency("pry", "~> 0.12.2")
25
31
  end
@@ -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(
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Log from an organizer with a custom logger" do
4
+ context "when overriding the global LightService organizer" do
5
+ let(:global_logger_organizer) do
6
+ Class.new do
7
+ extend LightService::Organizer
8
+
9
+ def self.call(number)
10
+ with(:number => number).reduce(actions)
11
+ end
12
+
13
+ def self.actions
14
+ [
15
+ TestDoubles::AddsOneAction,
16
+ TestDoubles::AddsTwoAction,
17
+ TestDoubles::AddsThreeAction
18
+ ]
19
+ end
20
+ end
21
+ end
22
+
23
+ let(:global_logger_string) { StringIO.new }
24
+
25
+ let(:custom_logger_string) { StringIO.new }
26
+ let(:custom_logger_organizer) do
27
+ custom_logger = Logger.new(custom_logger_string)
28
+
29
+ Class.new do
30
+ extend LightService::Organizer
31
+ log_with custom_logger
32
+
33
+ def self.call(coffee, this_hot = :very_hot)
34
+ with(:milk => this_hot, :coffee => coffee)
35
+ .reduce(TestDoubles::MakesLatteAction,
36
+ TestDoubles::AddsTwoActionWithFetch)
37
+ end
38
+ end
39
+ end
40
+
41
+ before do
42
+ @original_global_logger = LightService::Configuration.logger
43
+ LightService::Configuration.logger = Logger.new(global_logger_string)
44
+ end
45
+
46
+ it "logs in own logger" do
47
+ global_logger_organizer.call(1)
48
+ custom_logger_organizer.call(:coffee => "Cappucino")
49
+
50
+ expect(custom_logger_string.string).to include("MakesLatteAction")
51
+ expect(custom_logger_string.string).to_not include("AddsOneAction")
52
+ expect(global_logger_string.string).to include("AddsOneAction")
53
+ expect(global_logger_string.string).to_not include("MakesLatteAction")
54
+ end
55
+
56
+ after do
57
+ LightService::Configuration.logger = @original_global_logger
58
+ end
59
+ end
60
+ end
@@ -1,24 +1,50 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe "fail! returns immediately from executed block" do
4
- class FailAction
5
- extend LightService::Action
6
- promises :one, :two
7
-
8
- executed do |ctx|
9
- ctx.one = 1
10
- # Have to set it in Context
11
- ctx.two = nil
12
-
13
- ctx.fail_and_return!('Something went wrong')
14
- ctx.two = 2
3
+ RSpec.describe "fail_and_return!" do
4
+ describe "returns immediately from executed block" do
5
+ class FailAndReturnAction
6
+ extend LightService::Action
7
+ promises :one, :two
8
+
9
+ executed do |ctx|
10
+ ctx.one = 1
11
+ # Have to set it in Context
12
+ ctx.two = nil
13
+
14
+ ctx.fail_and_return!('Something went wrong')
15
+ ctx.two = 2
16
+ end
17
+ end
18
+
19
+ it "returns immediately from executed block" do
20
+ result = FailAndReturnAction.execute
21
+
22
+ expect(result).to be_failure
23
+ expect(result.two).to be_nil
15
24
  end
16
25
  end
17
26
 
18
- it "returns immediately from executed block" do
19
- result = FailAction.execute
27
+ describe "accepts error_code option" do
28
+ class FailAndReturnWithErrorCodeAction
29
+ extend LightService::Action
30
+ promises :one, :two
20
31
 
21
- expect(result).to be_failure
22
- expect(result.two).to be_nil
32
+ executed do |ctx|
33
+ ctx.one = 1
34
+ # Have to set it in Context
35
+ ctx.two = nil
36
+
37
+ ctx.fail_and_return!('Something went wrong', :error_code => 401)
38
+ ctx.two = 2
39
+ end
40
+ end
41
+
42
+ it "returned context contains the error_code" do
43
+ result = FailAndReturnWithErrorCodeAction.execute
44
+
45
+ expect(result).to be_failure
46
+ expect(result.error_code).to eq 401
47
+ expect(result.two).to be_nil
48
+ end
23
49
  end
24
50
  end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe LightService::Organizer do
4
+ class TestAddAliases
5
+ extend LightService::Organizer
6
+
7
+ def self.call(context = LightService::Context.make)
8
+ with(context).reduce(steps)
9
+ end
10
+
11
+ def self.steps
12
+ [
13
+ add_to_context(:my_message => 'Hello There'),
14
+ # This will add the alias `:a_message` which points
15
+ # to the :my_message key's value
16
+ add_aliases(:my_message => :a_message),
17
+ TestDoubles::CapitalizeMessage
18
+ ]
19
+ end
20
+ end
21
+
22
+ it 'adds aliases to the context embedded in the series of actions' do
23
+ result = TestAddAliases.call
24
+
25
+ expect(result).to be_success
26
+ expect(result.final_message).to eq('HELLO THERE')
27
+ end
28
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe LightService::Organizer do
4
+ class TestAddToContext
5
+ extend LightService::Organizer
6
+
7
+ def self.call(context = LightService::Context.make)
8
+ with(context).reduce(steps)
9
+ end
10
+
11
+ def self.steps
12
+ [
13
+ # This will add the `:number` key to the context
14
+ # with the value of 0, so it's available for
15
+ # AddsOneAction
16
+ add_to_context(:number => 0),
17
+ TestDoubles::AddsOneAction,
18
+ add_to_context(:something => 'hello')
19
+ ]
20
+ end
21
+ end
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
+
37
+ it 'adds items to the context on the fly' do
38
+ result = TestAddToContext.call
39
+
40
+ expect(result).to be_success
41
+ expect(result.number).to eq(1)
42
+ expect(result[:something]).to eq('hello')
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
57
+ end
@@ -14,7 +14,7 @@ RSpec.describe LightService::Organizer do
14
14
  TestDoubles::AddsOneAction,
15
15
  execute(->(ctx) { ctx.number += 1 }),
16
16
  execute(->(ctx) { ctx[:something] = 'hello' }),
17
- TestDoubles::AddsOneAction
17
+ TestDoubles::AddsOne.actions
18
18
  ]
19
19
  end
20
20
  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
 
@@ -48,4 +48,42 @@ RSpec.describe LightService::Organizer do
48
48
  result = TestReduceIf.call(empty_context)
49
49
  expect(result).to be_success
50
50
  end
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
+
58
+ it 'skips actions within in its own scope' do
59
+ org = Class.new do
60
+ extend LightService::Organizer
61
+
62
+ def self.call
63
+ reduce(actions)
64
+ end
65
+
66
+ def self.actions
67
+ [
68
+ reduce_if(
69
+ ->(c) { !c.nil? },
70
+ [
71
+ execute(->(c) { c[:first_reduce_if] = true }),
72
+ execute(->(c) { c.skip_remaining! }),
73
+ execute(->(c) { c[:second_reduce_if] = true })
74
+ ]
75
+ ),
76
+ execute(->(c) { c[:last_outside] = true })
77
+ ]
78
+ end
79
+ end
80
+
81
+ result = org.call
82
+
83
+ aggregate_failures do
84
+ expect(result[:first_reduce_if]).to be true
85
+ expect(result[:second_reduce_if]).to be_nil
86
+ expect(result[:last_outside]).to be true
87
+ end
88
+ end
51
89
  end