light-service 0.10.1 → 0.13.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 (35) 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 +39 -20
  7. data/RELEASES.md +17 -0
  8. data/gemfiles/activesupport_6.gemfile +8 -0
  9. data/lib/light-service.rb +1 -0
  10. data/lib/light-service/context.rb +4 -1
  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 +4 -5
  14. data/lib/light-service/organizer/with_reducer_factory.rb +11 -7
  15. data/lib/light-service/organizer/with_reducer_log_decorator.rb +2 -2
  16. data/lib/light-service/testing/context_factory.rb +12 -7
  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/reduce_if_spec.rb +32 -0
  26. data/spec/acceptance/testing/context_factory_spec.rb +12 -3
  27. data/spec/organizer_spec.rb +37 -14
  28. data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
  29. data/spec/spec_helper.rb +2 -1
  30. data/spec/test_doubles.rb +93 -0
  31. data/spec/testing/context_factory_spec.rb +20 -0
  32. metadata +32 -15
  33. data/gemfiles/activesupport_3.gemfile.lock +0 -76
  34. data/gemfiles/activesupport_4.gemfile.lock +0 -82
  35. data/gemfiles/activesupport_5.gemfile.lock +0 -82
@@ -1,3 +1,3 @@
1
1
  module LightService
2
- VERSION = "0.10.1".freeze
2
+ VERSION = "0.13.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
@@ -48,4 +48,36 @@ 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 'skips actions within in its own scope' do
53
+ org = Class.new do
54
+ extend LightService::Organizer
55
+
56
+ def self.call
57
+ reduce(actions)
58
+ end
59
+
60
+ def self.actions
61
+ [
62
+ reduce_if(
63
+ ->(c) { !c.nil? },
64
+ [
65
+ execute(->(c) { c[:first_reduce_if] = true }),
66
+ execute(->(c) { c.skip_remaining! }),
67
+ execute(->(c) { c[:second_reduce_if] = true })
68
+ ]
69
+ ),
70
+ execute(->(c) { c[:last_outside] = true })
71
+ ]
72
+ end
73
+ end
74
+
75
+ result = org.call
76
+
77
+ aggregate_failures do
78
+ expect(result[:first_reduce_if]).to be true
79
+ expect(result[:second_reduce_if]).to be_nil
80
+ expect(result[:last_outside]).to be true
81
+ end
82
+ end
51
83
  end
@@ -13,7 +13,7 @@ class AdditionOrganizerContextFactory
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,7 +21,7 @@ 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)
@@ -30,10 +30,19 @@ RSpec.describe TestDoubles::AddsThreeAction do
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
33
42
  end
34
43
 
35
44
  RSpec.describe TestDoubles::AddsTwoAction do
36
- it "does not execute a callback entirely from a ContextFactory" do
45
+ it 'does not execute a callback entirely from a ContextFactory' do
37
46
  context = LightService::Testing::ContextFactory
38
47
  .make_from(TestDoubles::CallbackOrganizer)
39
48
  .for(described_class)
@@ -44,20 +44,6 @@ describe LightService::Organizer do
44
44
  end
45
45
  end
46
46
 
47
- context "when no starting context is specified" do
48
- it "creates one implicitly" do
49
- expect(TestDoubles::AnAction).to receive(:execute)
50
- .with({})
51
- .and_return(ctx)
52
- expect(TestDoubles::AnotherAction).to receive(:execute)
53
- .with(ctx)
54
- .and_return(ctx)
55
-
56
- expect { TestDoubles::AnOrganizer.do_something_with_no_starting_context }
57
- .not_to raise_error
58
- end
59
- end
60
-
61
47
  context "when aliases are declared" do
62
48
  let(:organizer) do
63
49
  Class.new do
@@ -83,4 +69,41 @@ describe LightService::Organizer do
83
69
  organizer.call
84
70
  end
85
71
  end
72
+
73
+ context "when an organizer is nested and reduced within another" do
74
+ let(:reduced) { TestDoubles::NestingOrganizer.call(ctx) }
75
+ let(:organizer_result) do
76
+ TestDoubles::NotExplicitlyReturningContextOrganizer.call(ctx)
77
+ end
78
+
79
+ it "reduces an organizer which returns something" do
80
+ expect(organizer_result).to eq([1, 2, 3])
81
+ end
82
+
83
+ it "adds :foo and :bar to the context" do
84
+ reduced
85
+ expect(ctx[:foo]).to eq([1, 2, 3])
86
+ expect(ctx[:bar]).to eq(ctx[:foo])
87
+ end
88
+
89
+ it "returns the context" do
90
+ expect(reduced).to eq(ctx)
91
+ end
92
+ end
93
+
94
+ context 'can add items to the context' do
95
+ specify 'with #add_to_context' do
96
+ result = TestDoubles::AnOrganizerThatAddsToContext.call
97
+ expect(result[:strongest_avenger]).to eq 'The Thor'
98
+ expect(result[:last_jedi]).to eq 'Rey'
99
+ end
100
+ end
101
+
102
+ context 'can assign key aliaeses' do
103
+ it 'with #add_aliases' do
104
+ result = TestDoubles::AnOrganizerThatAddsAliases.call
105
+ expect(result[:foo]).to eq :bar
106
+ expect(result[:baz]).to eq :bar
107
+ end
108
+ end
86
109
  end
@@ -15,7 +15,7 @@ describe ProvidesFreeShippingAction do
15
15
  end
16
16
 
17
17
  context "when the order total with tax is <= 200" do
18
- specify "order gets free shipping" do
18
+ specify "order does not get free shipping" do
19
19
  allow(order).to receive_messages(:total_with_tax => 200)
20
20
  expect(order).not_to receive(:provide_free_shipping!)
21
21
 
@@ -15,8 +15,9 @@ end
15
15
  require 'light-service'
16
16
  require 'light-service/testing'
17
17
  require 'ostruct'
18
- require 'active_support/core_ext/string'
19
18
  require 'pry'
20
19
  require 'support'
20
+ require 'test_doubles'
21
+ require 'stringio'
21
22
 
22
23
  I18n.enforce_available_locales = true
@@ -102,6 +102,36 @@ module TestDoubles
102
102
  end
103
103
  end
104
104
 
105
+ class NotExplicitlyReturningContextOrganizer
106
+ extend LightService::Organizer
107
+
108
+ def self.call(context)
109
+ context[:foo] = [1, 2, 3]
110
+ end
111
+ end
112
+
113
+ class NestingOrganizer
114
+ extend LightService::Organizer
115
+
116
+ def self.call(context)
117
+ with(context).reduce(actions)
118
+ end
119
+
120
+ def self.actions
121
+ [NotExplicitlyReturningContextOrganizer, NestedAction]
122
+ end
123
+ end
124
+
125
+ class NestedAction
126
+ extend LightService::Action
127
+
128
+ expects :foo
129
+
130
+ executed do |context|
131
+ context[:bar] = context.foo
132
+ end
133
+ end
134
+
105
135
  class MakesTeaWithMilkAction
106
136
  extend LightService::Action
107
137
  expects :tea, :milk
@@ -223,6 +253,34 @@ module TestDoubles
223
253
  end
224
254
  end
225
255
 
256
+ class ExtraArgumentAdditionOrganizer
257
+ extend LightService::Organizer
258
+
259
+ def self.call(number, another_number)
260
+ with(:number => number + another_number).reduce(actions)
261
+ end
262
+
263
+ def self.actions
264
+ [
265
+ AddsOneAction,
266
+ AddsTwoAction,
267
+ AddsThreeAction
268
+ ]
269
+ end
270
+ end
271
+
272
+ class AddsOne
273
+ extend LightService::Organizer
274
+
275
+ def call(ctx)
276
+ with(ctx).reduce(actions)
277
+ end
278
+
279
+ def self.actions
280
+ [AddsOneAction]
281
+ end
282
+ end
283
+
226
284
  class AddsOneAction
227
285
  extend LightService::Action
228
286
  expects :number
@@ -497,4 +555,39 @@ module TestDoubles
497
555
  ctx.total += ctx.number
498
556
  end
499
557
  end
558
+
559
+ class CapitalizeMessage
560
+ extend LightService::Action
561
+ expects :a_message
562
+ promises :final_message
563
+
564
+ executed do |ctx|
565
+ ctx.final_message = ctx.a_message.upcase
566
+ end
567
+ end
568
+
569
+ class AnOrganizerThatAddsToContext
570
+ extend LightService::Organizer
571
+ def self.call
572
+ with.reduce(actions)
573
+ end
574
+
575
+ def self.actions
576
+ [add_to_context(
577
+ :strongest_avenger => 'The Thor',
578
+ :last_jedi => 'Rey'
579
+ )]
580
+ end
581
+ end
582
+
583
+ class AnOrganizerThatAddsAliases
584
+ extend LightService::Organizer
585
+ def self.call
586
+ with(:foo => :bar).reduce(actions)
587
+ end
588
+
589
+ def self.actions
590
+ [add_aliases(:foo => :baz)]
591
+ end
592
+ end
500
593
  end