light-service 0.10.2 → 0.14.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 (42) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +6 -0
  4. data/.travis.yml +12 -10
  5. data/Appraisals +4 -0
  6. data/README.md +61 -21
  7. data/RELEASES.md +16 -0
  8. data/gemfiles/activesupport_6.gemfile +8 -0
  9. data/lib/light-service.rb +1 -0
  10. data/lib/light-service/context.rb +6 -2
  11. data/lib/light-service/localization_adapter.rb +1 -1
  12. data/lib/light-service/organizer.rb +32 -0
  13. data/lib/light-service/organizer/with_reducer.rb +11 -6
  14. data/lib/light-service/organizer/with_reducer_factory.rb +11 -7
  15. data/lib/light-service/organizer/with_reducer_log_decorator.rb +5 -2
  16. data/lib/light-service/testing/context_factory.rb +19 -22
  17. data/lib/light-service/version.rb +1 -1
  18. data/light-service.gemspec +5 -4
  19. data/spec/acceptance/after_actions_spec.rb +13 -0
  20. data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
  21. data/spec/acceptance/fail_spec.rb +42 -16
  22. data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
  23. data/spec/acceptance/organizer/add_to_context_spec.rb +30 -0
  24. data/spec/acceptance/organizer/execute_spec.rb +1 -1
  25. data/spec/acceptance/organizer/iterate_spec.rb +7 -0
  26. data/spec/acceptance/organizer/reduce_if_spec.rb +38 -0
  27. data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
  28. data/spec/acceptance/testing/context_factory_spec.rb +25 -4
  29. data/spec/action_spec.rb +8 -0
  30. data/spec/organizer_spec.rb +42 -14
  31. data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
  32. data/spec/spec_helper.rb +2 -1
  33. data/spec/test_doubles.rb +186 -0
  34. data/spec/testing/context_factory/iterate_spec.rb +39 -0
  35. data/spec/testing/context_factory/reduce_if_spec.rb +40 -0
  36. data/spec/testing/context_factory/reduce_until_spec.rb +40 -0
  37. data/spec/testing/context_factory/with_callback_spec.rb +38 -0
  38. data/spec/testing/context_factory_spec.rb +28 -6
  39. metadata +40 -15
  40. data/gemfiles/activesupport_3.gemfile.lock +0 -76
  41. data/gemfiles/activesupport_4.gemfile.lock +0 -82
  42. data/gemfiles/activesupport_5.gemfile.lock +0 -82
@@ -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,17 +1,6 @@
1
1
  module LightService
2
2
  module Testing
3
3
  class ContextFactory
4
- class ContextFactoryOrganizer
5
- extend LightService::Organizer
6
- class << self
7
- attr_accessor :actions
8
- end
9
-
10
- def self.call(ctx)
11
- with(ctx).reduce(actions)
12
- end
13
- end
14
-
15
4
  attr_reader :organizer
16
5
 
17
6
  def self.make_from(organizer)
@@ -19,25 +8,33 @@ module LightService
19
8
  end
20
9
 
21
10
  def for(action)
22
- ContextFactoryOrganizer.actions = find_up_to(action)
11
+ @organizer.append_before_actions(
12
+ lambda do |ctx|
13
+ if ctx.current_action == action
14
+ # The last `:_before_actions` hook is for
15
+ # ContextFactory, remove it, so it won't
16
+ # be invoked again
17
+ ctx[:_before_actions].pop
18
+
19
+ throw(:return_ctx_from_execution, ctx)
20
+ end
21
+ end
22
+ )
23
+
23
24
  self
24
25
  end
25
26
 
26
- def with(ctx)
27
- ContextFactoryOrganizer.call(ctx)
27
+ # More than one arguments can be passed to the
28
+ # Organizer's #call method
29
+ def with(*args, &block)
30
+ catch(:return_ctx_from_execution) do
31
+ @organizer.call(*args, &block)
32
+ end
28
33
  end
29
34
 
30
35
  def initialize(organizer)
31
36
  @organizer = organizer
32
37
  end
33
-
34
- def find_up_to(action)
35
- original_actions = organizer.actions
36
-
37
- original_actions.take_while do |current_action|
38
- current_action != action
39
- end
40
- end
41
38
  end
42
39
  end
43
40
  end
@@ -1,3 +1,3 @@
1
1
  module LightService
2
- VERSION = "0.10.2".freeze
2
+ VERSION = "0.14.0".freeze
3
3
  end
@@ -16,10 +16,11 @@ 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
21
  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")
22
+ gem.add_development_dependency("simplecov", "~> 0.17")
23
+ gem.add_development_dependency("rubocop", "~> 0.68.0")
24
+ gem.add_development_dependency("rubocop-performance", "~> 1.2.0")
25
+ gem.add_development_dependency("pry", "~> 0.12.2")
25
26
  end
@@ -64,4 +64,17 @@ RSpec.describe 'Action after_actions' do
64
64
  expect(result.fetch(:number)).to eq(1)
65
65
  end
66
66
  end
67
+
68
+ describe 'after_actions can be appended' do
69
+ it 'adds to the :_after_actions collection' do
70
+ TestDoubles::AdditionOrganizer.append_after_actions(
71
+ lambda do |ctx|
72
+ ctx.number -= 3 if ctx.current_action == TestDoubles::AddsThreeAction
73
+ end
74
+ )
75
+
76
+ result = TestDoubles::AdditionOrganizer.call(0)
77
+ expect(result.fetch(:number)).to eq(3)
78
+ end
79
+ end
67
80
  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
@@ -8,12 +8,12 @@ class AdditionOrganizerContextFactory
8
8
  LightService::Testing::ContextFactory
9
9
  .make_from(TestDoubles::AdditionOrganizer)
10
10
  .for(action)
11
- .with(:number => number)
11
+ .with(number)
12
12
  end
13
13
  end
14
14
 
15
15
  RSpec.describe TestDoubles::AddsThreeAction do
16
- it "creates a context for the action with ContextFactory wrapper" do
16
+ it 'creates a context for the action with ContextFactory wrapper' do
17
17
  context =
18
18
  AdditionOrganizerContextFactory
19
19
  .make_for(TestDoubles::AddsThreeAction, 1)
@@ -21,13 +21,34 @@ RSpec.describe TestDoubles::AddsThreeAction do
21
21
  expect(context.number).to eq(7)
22
22
  end
23
23
 
24
- it "creates a context for the action using the ContextFactory" do
24
+ it 'creates a context for the action using the ContextFactory' do
25
25
  context =
26
26
  LightService::Testing::ContextFactory
27
27
  .make_from(TestDoubles::AdditionOrganizer)
28
28
  .for(TestDoubles::AddsThreeAction)
29
- .with(:number => 4) # Context is a "glorified" hash
29
+ .with(4) # Context is a "glorified" hash
30
30
 
31
31
  expect(context.number).to eq(7)
32
32
  end
33
+
34
+ it "works with multiple arguments passed to Organizer's call method" do
35
+ context = LightService::Testing::ContextFactory
36
+ .make_from(TestDoubles::ExtraArgumentAdditionOrganizer)
37
+ .for(described_class)
38
+ .with(4, 2)
39
+
40
+ expect(context.number).to eq(9)
41
+ end
42
+ end
43
+
44
+ RSpec.describe TestDoubles::AddsTwoAction do
45
+ it 'does not execute a callback entirely from a ContextFactory' do
46
+ context = LightService::Testing::ContextFactory
47
+ .make_from(TestDoubles::CallbackOrganizer)
48
+ .for(described_class)
49
+ .with(:number => 0)
50
+
51
+ # add 1, add 10, then stop before executing first add 2
52
+ expect(context.number).to eq(11)
53
+ end
33
54
  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" }