light-service 0.10.2 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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" }