light-service 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|