light-service 0.10.3 → 0.15.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 (49) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +6 -0
  4. data/.travis.yml +12 -11
  5. data/Appraisals +4 -4
  6. data/Gemfile +0 -2
  7. data/README.md +240 -34
  8. data/RELEASES.md +20 -1
  9. data/gemfiles/activesupport_4.gemfile +0 -1
  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/context.rb +6 -2
  21. data/lib/light-service/localization_adapter.rb +1 -1
  22. data/lib/light-service/organizer.rb +18 -0
  23. data/lib/light-service/organizer/with_reducer.rb +11 -6
  24. data/lib/light-service/organizer/with_reducer_factory.rb +11 -7
  25. data/lib/light-service/organizer/with_reducer_log_decorator.rb +5 -2
  26. data/lib/light-service/version.rb +1 -1
  27. data/light-service.gemspec +9 -4
  28. data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
  29. data/spec/acceptance/fail_spec.rb +42 -16
  30. data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
  31. data/spec/acceptance/organizer/add_to_context_spec.rb +30 -0
  32. data/spec/acceptance/organizer/execute_spec.rb +1 -1
  33. data/spec/acceptance/organizer/iterate_spec.rb +7 -0
  34. data/spec/acceptance/organizer/reduce_if_spec.rb +38 -0
  35. data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
  36. data/spec/action_spec.rb +8 -0
  37. data/spec/lib/generators/action_generator_advanced_spec.rb +43 -0
  38. data/spec/lib/generators/action_generator_simple_spec.rb +37 -0
  39. data/spec/lib/generators/full_generator_test_blobs.rb +193 -0
  40. data/spec/lib/generators/organizer_generator_advanced_spec.rb +37 -0
  41. data/spec/lib/generators/organizer_generator_simple_spec.rb +37 -0
  42. data/spec/organizer_spec.rb +42 -14
  43. data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
  44. data/spec/spec_helper.rb +7 -2
  45. data/spec/test_doubles.rb +77 -0
  46. metadata +104 -15
  47. data/gemfiles/activesupport_3.gemfile.lock +0 -76
  48. data/gemfiles/activesupport_4.gemfile.lock +0 -82
  49. data/gemfiles/activesupport_5.gemfile.lock +0 -82
@@ -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,19 +29,18 @@ 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
- actions.reduce(context) do |current_context, action|
35
+ actions.each_with_object(context) do |action, current_context|
29
36
  begin
30
- result = invoke_action(current_context, action)
37
+ invoke_action(current_context, action)
31
38
  rescue FailWithRollbackError
32
- result = reduce_rollback(actions)
39
+ reduce_rollback(actions)
33
40
  ensure
34
41
  # For logging
35
42
  yield(current_context, action) if block_given?
36
43
  end
37
-
38
- result
39
44
  end
40
45
  end
41
46
 
@@ -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.10.3".freeze
2
+ VERSION = "0.15.0".freeze
3
3
  end
@@ -16,10 +16,15 @@ Gem::Specification.new do |gem|
16
16
  gem.require_paths = ["lib"]
17
17
  gem.version = LightService::VERSION
18
18
 
19
- gem.add_dependency("activesupport", ">= 3.0")
19
+ gem.add_runtime_dependency("activesupport", ">= 3.0.0")
20
20
 
21
+ gem.add_development_dependency("generator_spec", "~> 0.9.4")
22
+ gem.add_development_dependency("test-unit", "~> 3.0") # Needed for generator specs.
23
+ gem.add_development_dependency("appraisal", "~> 2.3")
21
24
  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")
25
+ gem.add_development_dependency("simplecov", "~> 0.17")
26
+ gem.add_development_dependency("codecov", "~> 0.1")
27
+ gem.add_development_dependency("rubocop", "~> 0.68.0")
28
+ gem.add_development_dependency("rubocop-performance", "~> 1.2.0")
29
+ gem.add_development_dependency("pry", "~> 0.12.2")
25
30
  end
@@ -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,30 @@
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
+ it 'adds items to the context on the fly' do
24
+ result = TestAddToContext.call
25
+
26
+ expect(result).to be_success
27
+ expect(result.number).to eq(1)
28
+ expect(result[:something]).to eq('hello')
29
+ end
30
+ 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
@@ -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
@@ -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
@@ -68,6 +68,14 @@ describe LightService::Action do
68
68
  expect(result.to_hash).to eq(:number => 2)
69
69
  end
70
70
 
71
+ context "when called directly" do
72
+ it "is expected to not be organized" do
73
+ result = TestDoubles::AddsTwoActionWithFetch.execute(context)
74
+
75
+ expect(result.organized_by).to be_nil
76
+ end
77
+ end
78
+
71
79
  context "when invoked with hash" do
72
80
  it "creates LightService::Context implicitly" do
73
81
  ctx = { :some_key => "some value" }
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ require_relative '../../../lib/generators/light_service/action_generator.rb'
4
+ require_relative './full_generator_test_blobs'
5
+
6
+ describe LightService::Generators::ActionGenerator, :type => :generator do
7
+ destination File.expand_path('tmp', __dir__)
8
+
9
+ context "when generating an advanced action" do
10
+ before(:all) do
11
+ prepare_destination
12
+ run_generator
13
+ end
14
+
15
+ after(:all) do
16
+ FileUtils.rm_rf destination_root
17
+ end
18
+
19
+ arguments %w[
20
+ my/fancy/action
21
+ expects:foo,bar
22
+ promises:baz,qux
23
+ --no-roll-back
24
+ --dir=services
25
+ ]
26
+
27
+ specify do
28
+ expect(destination_root).to(have_structure do
29
+ directory "app/services/my/fancy" do
30
+ file "action.rb" do
31
+ contains FullGeneratorTestBlobs.advanced_action_blob
32
+ end
33
+ end
34
+
35
+ directory "spec/services/my/fancy" do
36
+ file "action_spec.rb" do
37
+ contains FullGeneratorTestBlobs.advanced_action_spec_blob
38
+ end
39
+ end
40
+ end)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ require_relative '../../../lib/generators/light_service/action_generator.rb'
4
+ require_relative './full_generator_test_blobs'
5
+
6
+ describe LightService::Generators::ActionGenerator, :type => :generator do
7
+ destination File.expand_path('tmp', __dir__)
8
+
9
+ context "when generating a simple action" do
10
+ before(:all) do
11
+ prepare_destination
12
+ run_generator
13
+ end
14
+
15
+ after(:all) do
16
+ FileUtils.rm_rf destination_root
17
+ end
18
+
19
+ arguments %w[my_action]
20
+
21
+ specify do
22
+ expect(destination_root).to(have_structure do
23
+ directory "app/actions" do
24
+ file "my_action.rb" do
25
+ contains FullGeneratorTestBlobs.simple_action_blob
26
+ end
27
+ end
28
+
29
+ directory "spec/actions" do
30
+ file "my_action_spec.rb" do
31
+ contains FullGeneratorTestBlobs.simple_action_spec_blob
32
+ end
33
+ end
34
+ end)
35
+ end
36
+ end
37
+ end