light-service 0.6.0 → 0.6.1
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +34 -0
- data/.travis.yml +16 -2
- data/Appraisals +7 -0
- data/Gemfile +2 -0
- data/README.md +126 -16
- data/RELEASES.md +4 -0
- data/Rakefile +3 -0
- data/gemfiles/activesupport_3.gemfile +8 -0
- data/gemfiles/activesupport_3.gemfile.lock +63 -0
- data/gemfiles/activesupport_4.gemfile +8 -0
- data/gemfiles/activesupport_4.gemfile.lock +71 -0
- data/lib/light-service.rb +1 -1
- data/lib/light-service/action.rb +6 -8
- data/lib/light-service/configuration.rb +0 -2
- data/lib/light-service/context.rb +32 -22
- data/lib/light-service/context/key_verifier.rb +87 -83
- data/lib/light-service/localization_adapter.rb +10 -7
- data/lib/light-service/organizer.rb +6 -3
- data/lib/light-service/organizer/with_reducer.rb +53 -39
- data/lib/light-service/organizer/with_reducer_factory.rb +3 -4
- data/lib/light-service/organizer/with_reducer_log_decorator.rb +81 -51
- data/lib/light-service/version.rb +2 -1
- data/light-service.gemspec +4 -4
- data/resources/fail_actions.png +0 -0
- data/resources/skip_actions.png +0 -0
- data/spec/acceptance/around_each_spec.rb +27 -0
- data/spec/acceptance/include_warning_spec.rb +6 -2
- data/spec/acceptance/log_from_organizer_spec.rb +39 -18
- data/spec/acceptance/message_localization_spec.rb +23 -23
- data/spec/acceptance/rollback_spec.rb +1 -3
- data/spec/action_expected_keys_spec.rb +32 -19
- data/spec/action_promised_keys_spec.rb +72 -54
- data/spec/action_spec.rb +23 -5
- data/spec/context_spec.rb +21 -17
- data/spec/localization_adapter_spec.rb +14 -10
- data/spec/organizer/with_reducer_spec.rb +19 -2
- data/spec/organizer_key_aliases_spec.rb +6 -5
- data/spec/organizer_spec.rb +32 -56
- data/spec/sample/calculates_tax_spec.rb +17 -9
- data/spec/sample/provides_free_shipping_action_spec.rb +3 -7
- data/spec/sample/tax/calculates_order_tax_action.rb +3 -2
- data/spec/sample/tax/calculates_tax.rb +3 -4
- data/spec/sample/tax/looks_up_tax_percentage_action.rb +10 -8
- data/spec/sample/tax/provides_free_shipping_action.rb +2 -4
- data/spec/spec_helper.rb +0 -1
- data/spec/test_doubles.rb +38 -15
- metadata +38 -28
@@ -15,18 +15,20 @@ describe LightService::LocalizationAdapter do
|
|
15
15
|
expected_scope = "test_doubles/an_action.light_service.failures"
|
16
16
|
|
17
17
|
expect(I18n).to receive(:t)
|
18
|
-
|
19
|
-
|
18
|
+
.with(message_or_key, :scope => expected_scope)
|
19
|
+
.and_return("message")
|
20
20
|
|
21
21
|
expect(subject).to eq("message")
|
22
22
|
end
|
23
23
|
|
24
24
|
it "allows passing interpolation options to I18n layer" do
|
25
25
|
expect(I18n).to receive(:t)
|
26
|
-
|
27
|
-
|
26
|
+
.with(message_or_key, hash_including(:i18n_variable => "value"))
|
27
|
+
.and_return("message")
|
28
28
|
|
29
|
-
subject = adapter.failure(message_or_key,
|
29
|
+
subject = adapter.failure(message_or_key,
|
30
|
+
action_class,
|
31
|
+
:i18n_variable => "value")
|
30
32
|
|
31
33
|
expect(subject).to eq("message")
|
32
34
|
end
|
@@ -51,18 +53,20 @@ describe LightService::LocalizationAdapter do
|
|
51
53
|
expected_scope = "test_doubles/an_action.light_service.successes"
|
52
54
|
|
53
55
|
expect(I18n).to receive(:t)
|
54
|
-
|
55
|
-
|
56
|
+
.with(message_or_key, :scope => expected_scope)
|
57
|
+
.and_return("message")
|
56
58
|
|
57
59
|
expect(subject).to eq("message")
|
58
60
|
end
|
59
61
|
|
60
62
|
it "allows passing interpolation options to I18n layer" do
|
61
63
|
expect(I18n).to receive(:t)
|
62
|
-
|
63
|
-
|
64
|
+
.with(message_or_key, hash_including(:i18n_variable => "value"))
|
65
|
+
.and_return("message")
|
64
66
|
|
65
|
-
subject = adapter.success(message_or_key,
|
67
|
+
subject = adapter.success(message_or_key,
|
68
|
+
action_class,
|
69
|
+
:i18n_variable => "value")
|
66
70
|
|
67
71
|
expect(subject).to eq("message")
|
68
72
|
end
|
@@ -19,10 +19,25 @@ describe LightService::Organizer::WithReducer do
|
|
19
19
|
expect(result).to be_success
|
20
20
|
end
|
21
21
|
|
22
|
+
it "executes a handler around each action and continues reducing" do
|
23
|
+
expect(action1).to receive(:execute).with(context).and_return(context)
|
24
|
+
expect(TestDoubles::AroundEachNullHandler).to receive(:call)
|
25
|
+
.with(action1, context).and_yield
|
26
|
+
|
27
|
+
result = described_class.new.with(context)
|
28
|
+
.around_each(TestDoubles::AroundEachNullHandler)
|
29
|
+
.reduce([action1])
|
30
|
+
|
31
|
+
expect(result).to eq(context)
|
32
|
+
expect(result).to be_success
|
33
|
+
end
|
34
|
+
|
22
35
|
context "when FailWithRollbackError is caught" do
|
23
36
|
it "reduces the rollback" do
|
24
37
|
expect(action1).to receive(:execute).with(context).and_return(context)
|
25
|
-
expect(action2).to receive(:execute).with(context)
|
38
|
+
expect(action2).to receive(:execute).with(context) do
|
39
|
+
fail LightService::FailWithRollbackError
|
40
|
+
end
|
26
41
|
expect(action1).to receive(:rollback).with(context).and_return(context)
|
27
42
|
expect(action2).to receive(:rollback).with(context).and_return(context)
|
28
43
|
|
@@ -33,7 +48,9 @@ describe LightService::Organizer::WithReducer do
|
|
33
48
|
|
34
49
|
it "reduces the rollback with an action without `rollback`" do
|
35
50
|
expect(action1).to receive(:execute).with(context).and_return(context)
|
36
|
-
expect(action2).to receive(:execute).with(context)
|
51
|
+
expect(action2).to receive(:execute).with(context) do
|
52
|
+
fail LightService::FailWithRollbackError
|
53
|
+
end
|
37
54
|
expect(action2).to receive(:rollback).with(context).and_return(context)
|
38
55
|
|
39
56
|
result = described_class.new.with(context).reduce(actions)
|
@@ -8,11 +8,12 @@ describe "organizer aliases macro" do
|
|
8
8
|
|
9
9
|
aliases :promised_key => :expected_key
|
10
10
|
|
11
|
-
def self.do_something(ctx={})
|
12
|
-
with(ctx).reduce(
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
def self.do_something(ctx = {})
|
12
|
+
with(ctx).reduce(
|
13
|
+
[
|
14
|
+
TestDoubles::PromisesPromisedKeyAction,
|
15
|
+
TestDoubles::ExpectsExpectedKeyAction
|
16
|
+
])
|
16
17
|
end
|
17
18
|
end
|
18
19
|
end
|
data/spec/organizer_spec.rb
CHANGED
@@ -2,84 +2,62 @@ require 'spec_helper'
|
|
2
2
|
require 'test_doubles'
|
3
3
|
|
4
4
|
describe LightService::Organizer do
|
5
|
-
|
6
|
-
let(:context) { LightService::Context.make(:user => user) }
|
5
|
+
let(:ctx) { LightService::Context.make(:user => user) }
|
7
6
|
let(:user) { double(:user) }
|
8
7
|
|
9
8
|
context "when #with is called with hash" do
|
10
9
|
before do
|
11
|
-
expect(TestDoubles::AnAction).to receive(:execute)
|
12
|
-
|
13
|
-
|
14
|
-
expect(TestDoubles::AnotherAction).to receive(:execute)
|
15
|
-
|
16
|
-
|
10
|
+
expect(TestDoubles::AnAction).to receive(:execute)
|
11
|
+
.with(ctx)
|
12
|
+
.and_return(ctx)
|
13
|
+
expect(TestDoubles::AnotherAction).to receive(:execute)
|
14
|
+
.with(ctx)
|
15
|
+
.and_return(ctx)
|
17
16
|
end
|
18
17
|
|
19
18
|
it "implicitly creates a Context" do
|
20
19
|
result = TestDoubles::AnOrganizer.do_something(:user => user)
|
21
|
-
expect(result).to eq(
|
20
|
+
expect(result).to eq(ctx)
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
25
24
|
context "when #with is called with Context" do
|
26
25
|
before do
|
27
|
-
expect(TestDoubles::AnAction).to receive(:execute)
|
28
|
-
|
29
|
-
|
30
|
-
expect(TestDoubles::AnotherAction).to receive(:execute)
|
31
|
-
|
32
|
-
|
26
|
+
expect(TestDoubles::AnAction).to receive(:execute)
|
27
|
+
.with(ctx)
|
28
|
+
.and_return(ctx)
|
29
|
+
expect(TestDoubles::AnotherAction).to receive(:execute)
|
30
|
+
.with(ctx)
|
31
|
+
.and_return(ctx)
|
33
32
|
end
|
34
33
|
|
35
34
|
it "uses that Context without recreating it" do
|
36
|
-
result = TestDoubles::AnOrganizer.do_something(
|
37
|
-
expect(result).to eq(
|
35
|
+
result = TestDoubles::AnOrganizer.do_something(ctx)
|
36
|
+
expect(result).to eq(ctx)
|
38
37
|
end
|
39
38
|
end
|
40
39
|
|
41
40
|
context "when no Actions are specified" do
|
42
41
|
it "throws a Runtime error" do
|
43
|
-
expect { TestDoubles::AnOrganizer.do_something_with_no_actions(
|
44
|
-
raise_error
|
42
|
+
expect { TestDoubles::AnOrganizer.do_something_with_no_actions(ctx) }.to \
|
43
|
+
raise_error(RuntimeError, "No action(s) were provided")
|
45
44
|
end
|
46
45
|
end
|
47
46
|
|
48
47
|
context "when no starting context is specified" do
|
49
48
|
it "creates one implicitly" do
|
50
|
-
expect(TestDoubles::AnAction).to receive(:execute)
|
51
|
-
.with({})
|
52
|
-
.and_return(
|
53
|
-
expect(TestDoubles::AnotherAction).to receive(:execute)
|
54
|
-
.with(
|
55
|
-
.and_return(
|
56
|
-
|
57
|
-
expect { TestDoubles::AnOrganizer.do_something_with_no_starting_context }
|
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 }
|
58
57
|
.not_to raise_error
|
59
58
|
end
|
60
59
|
end
|
61
60
|
|
62
|
-
context "when the organizer is also an action", "and the organizer rescues exceptions of its sub-actions" do
|
63
|
-
let(:organizer) do
|
64
|
-
Class.new do
|
65
|
-
extend LightService::Organizer
|
66
|
-
extend LightService::Action
|
67
|
-
|
68
|
-
executed do |ctx|
|
69
|
-
begin
|
70
|
-
with(ctx).
|
71
|
-
reduce(TestDoubles::MakesTeaPromisingKeyButRaisesException)
|
72
|
-
rescue
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
it "does not raise an error concerning a sub-action's missing keys" do
|
79
|
-
expect { organizer.execute }.not_to raise_error
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
61
|
context "when aliases are declared" do
|
84
62
|
let(:organizer) do
|
85
63
|
Class.new do
|
@@ -93,18 +71,16 @@ describe LightService::Organizer do
|
|
93
71
|
end
|
94
72
|
|
95
73
|
it "merges the aliases into the data" do
|
96
|
-
with_reducer = double(reduce
|
74
|
+
with_reducer = double(:reduce => true)
|
97
75
|
|
98
|
-
allow(described_class::WithReducerFactory).to receive(:make)
|
99
|
-
and_return(with_reducer)
|
76
|
+
allow(described_class::WithReducerFactory).to receive(:make)
|
77
|
+
.and_return(with_reducer)
|
100
78
|
|
101
|
-
expect(with_reducer).to
|
102
|
-
|
103
|
-
|
104
|
-
and_return(with_reducer)
|
79
|
+
expect(with_reducer).to receive(:with)
|
80
|
+
.with(hash_including(:_aliases => { :foo => :bar }))
|
81
|
+
.and_return(with_reducer)
|
105
82
|
|
106
83
|
organizer.do_stuff
|
107
84
|
end
|
108
85
|
end
|
109
|
-
|
110
86
|
end
|
@@ -6,20 +6,28 @@ require_relative 'tax/provides_free_shipping_action'
|
|
6
6
|
|
7
7
|
describe CalculatesTax do
|
8
8
|
let(:order) { double('order') }
|
9
|
-
let(:
|
10
|
-
|
9
|
+
let(:ctx) do
|
10
|
+
double('ctx', :keys => [:user],
|
11
|
+
:failure? => false, :skip_all? => false)
|
12
|
+
end
|
11
13
|
|
12
14
|
it "calls the actions in order" do
|
13
|
-
allow(
|
14
|
-
|
15
|
-
|
15
|
+
allow(LightService::Context).to receive(:make)
|
16
|
+
.with(:order => order)
|
17
|
+
.and_return(ctx)
|
16
18
|
|
17
|
-
allow(LooksUpTaxPercentageAction).to receive(:execute)
|
18
|
-
|
19
|
-
|
19
|
+
allow(LooksUpTaxPercentageAction).to receive(:execute)
|
20
|
+
.with(ctx)
|
21
|
+
.and_return(ctx)
|
22
|
+
allow(CalculatesOrderTaxAction).to receive(:execute)
|
23
|
+
.with(ctx)
|
24
|
+
.and_return(ctx)
|
25
|
+
allow(ProvidesFreeShippingAction).to receive(:execute)
|
26
|
+
.with(ctx)
|
27
|
+
.and_return(ctx)
|
20
28
|
|
21
29
|
result = CalculatesTax.for_order(order)
|
22
30
|
|
23
|
-
expect(result).to eq
|
31
|
+
expect(result).to eq(ctx)
|
24
32
|
end
|
25
33
|
end
|
@@ -3,17 +3,14 @@ require_relative 'tax/provides_free_shipping_action'
|
|
3
3
|
|
4
4
|
describe ProvidesFreeShippingAction do
|
5
5
|
let(:order) { double('order') }
|
6
|
-
let(:
|
7
|
-
data = { :order => order }
|
8
|
-
::LightService::Context.make(data)
|
9
|
-
end
|
6
|
+
let(:ctx) { { :order => order } }
|
10
7
|
|
11
8
|
context "when the order total with tax is > 200" do
|
12
9
|
specify "order gets free shipping" do
|
13
10
|
allow(order).to receive_messages(:total_with_tax => 201)
|
14
11
|
expect(order).to receive(:provide_free_shipping!)
|
15
12
|
|
16
|
-
ProvidesFreeShippingAction.execute(
|
13
|
+
ProvidesFreeShippingAction.execute(ctx)
|
17
14
|
end
|
18
15
|
end
|
19
16
|
|
@@ -22,8 +19,7 @@ describe ProvidesFreeShippingAction do
|
|
22
19
|
allow(order).to receive_messages(:total_with_tax => 200)
|
23
20
|
expect(order).not_to receive(:provide_free_shipping!)
|
24
21
|
|
25
|
-
ProvidesFreeShippingAction.execute(
|
22
|
+
ProvidesFreeShippingAction.execute(ctx)
|
26
23
|
end
|
27
24
|
end
|
28
|
-
|
29
25
|
end
|
@@ -2,7 +2,8 @@ class CalculatesOrderTaxAction
|
|
2
2
|
extend ::LightService::Action
|
3
3
|
expects :order, :tax_percentage
|
4
4
|
|
5
|
-
executed do |
|
6
|
-
|
5
|
+
executed do |ctx|
|
6
|
+
order_total = (ctx.order.total * (ctx.tax_percentage / 100))
|
7
|
+
ctx.order.tax = order_total.round(2)
|
7
8
|
end
|
8
9
|
end
|
@@ -3,10 +3,9 @@ class CalculatesTax
|
|
3
3
|
|
4
4
|
def self.for_order(order)
|
5
5
|
with(:order => order).reduce(
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
LooksUpTaxPercentageAction,
|
7
|
+
CalculatesOrderTaxAction,
|
8
|
+
ProvidesFreeShippingAction
|
9
9
|
)
|
10
10
|
end
|
11
11
|
end
|
12
|
-
|
@@ -3,23 +3,25 @@ class LooksUpTaxPercentageAction
|
|
3
3
|
expects :order
|
4
4
|
promises :tax_percentage
|
5
5
|
|
6
|
-
executed do |
|
7
|
-
tax_ranges = TaxRange.for_region(
|
8
|
-
|
6
|
+
executed do |ctx|
|
7
|
+
tax_ranges = TaxRange.for_region(ctx.order.region)
|
8
|
+
ctx.tax_percentage = 0
|
9
9
|
|
10
|
-
next
|
10
|
+
next ctx if object_is_nil?(tax_ranges, ctx, 'The tax ranges were not found')
|
11
11
|
|
12
|
-
|
12
|
+
ctx.tax_percentage = tax_ranges.for_total(ctx.order.total)
|
13
13
|
|
14
|
-
|
14
|
+
error_message = 'The tax percentage was not found'
|
15
|
+
next ctx if object_is_nil?(ctx.tax_percentage, ctx, error_message)
|
15
16
|
end
|
16
17
|
|
17
|
-
def self.object_is_nil?(object,
|
18
|
+
def self.object_is_nil?(object, ctx, message)
|
18
19
|
if object.nil?
|
19
|
-
|
20
|
+
ctx.fail!(message)
|
20
21
|
return true
|
21
22
|
end
|
22
23
|
|
23
24
|
false
|
24
25
|
end
|
26
|
+
private_class_method :object_is_nil?
|
25
27
|
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/test_doubles.rb
CHANGED
@@ -1,6 +1,31 @@
|
|
1
1
|
# A collection of Action and Organizer dummies used in specs
|
2
2
|
|
3
3
|
module TestDoubles
|
4
|
+
class AroundEachNullHandler
|
5
|
+
def self.call(_action, _context)
|
6
|
+
yield
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class AroundEachLoggerHandler
|
11
|
+
def self.call(action, context)
|
12
|
+
MyLogger.info(action, context)
|
13
|
+
result = yield
|
14
|
+
MyLogger.info(action, context)
|
15
|
+
|
16
|
+
result
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class AroundEachOrganizer
|
21
|
+
extend LightService::Organizer
|
22
|
+
def self.add(action_arguments)
|
23
|
+
with(action_arguments)
|
24
|
+
.around_each(AroundEachLoggerHandler)
|
25
|
+
.reduce([AddsTwoActionWithFetch])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
4
29
|
class AddsTwoActionWithFetch
|
5
30
|
extend LightService::Action
|
6
31
|
|
@@ -46,7 +71,8 @@ module TestDoubles
|
|
46
71
|
promises :milk_tea
|
47
72
|
|
48
73
|
executed do |context|
|
49
|
-
context.milk_tea = "#{context.tea} - #{context.milk}
|
74
|
+
context.milk_tea = "#{context.tea} - #{context.milk}"\
|
75
|
+
" - with #{context.chocolate}"
|
50
76
|
end
|
51
77
|
end
|
52
78
|
|
@@ -68,7 +94,8 @@ module TestDoubles
|
|
68
94
|
end
|
69
95
|
|
70
96
|
if context.milk == :super_hot
|
71
|
-
|
97
|
+
error_message = "Can't make a latte from a milk that's super hot!"
|
98
|
+
context.fail_with_rollback!(error_message)
|
72
99
|
next context
|
73
100
|
end
|
74
101
|
|
@@ -98,8 +125,8 @@ module TestDoubles
|
|
98
125
|
|
99
126
|
def self.call(tea, milk, coffee)
|
100
127
|
with(:tea => tea, :milk => milk, :coffee => coffee)
|
101
|
-
|
102
|
-
|
128
|
+
.reduce(TestDoubles::MakesTeaWithMilkAction,
|
129
|
+
TestDoubles::MakesLatteAction)
|
103
130
|
end
|
104
131
|
end
|
105
132
|
|
@@ -108,8 +135,8 @@ module TestDoubles
|
|
108
135
|
|
109
136
|
def self.call(milk, coffee)
|
110
137
|
with(:milk => milk, :coffee => coffee)
|
111
|
-
|
112
|
-
|
138
|
+
.reduce(TestDoubles::AddsTwoActionWithFetch,
|
139
|
+
TestDoubles::MakesLatteAction)
|
113
140
|
end
|
114
141
|
end
|
115
142
|
|
@@ -118,9 +145,8 @@ module TestDoubles
|
|
118
145
|
|
119
146
|
def self.call(coffee, this_hot = :very_hot)
|
120
147
|
with(:milk => this_hot, :coffee => coffee)
|
121
|
-
|
122
|
-
|
123
|
-
|
148
|
+
.reduce(TestDoubles::MakesLatteAction,
|
149
|
+
TestDoubles::AddsTwoActionWithFetch)
|
124
150
|
end
|
125
151
|
end
|
126
152
|
|
@@ -129,9 +155,8 @@ module TestDoubles
|
|
129
155
|
|
130
156
|
def self.call(coffee)
|
131
157
|
with(:milk => "5%", :coffee => coffee)
|
132
|
-
|
133
|
-
|
134
|
-
|
158
|
+
.reduce(TestDoubles::MakesLatteAction,
|
159
|
+
TestDoubles::AddsTwoActionWithFetch)
|
135
160
|
end
|
136
161
|
end
|
137
162
|
|
@@ -222,11 +247,10 @@ module TestDoubles
|
|
222
247
|
context.product = make_product
|
223
248
|
end
|
224
249
|
|
225
|
-
private
|
226
|
-
|
227
250
|
def self.make_product
|
228
251
|
fail "Fail"
|
229
252
|
end
|
253
|
+
private_class_method :make_product
|
230
254
|
end
|
231
255
|
|
232
256
|
class PromisesPromisedKeyAction
|
@@ -249,5 +273,4 @@ module TestDoubles
|
|
249
273
|
ctx.final_key = ctx.expected_key
|
250
274
|
end
|
251
275
|
end
|
252
|
-
|
253
276
|
end
|