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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +34 -0
  3. data/.travis.yml +16 -2
  4. data/Appraisals +7 -0
  5. data/Gemfile +2 -0
  6. data/README.md +126 -16
  7. data/RELEASES.md +4 -0
  8. data/Rakefile +3 -0
  9. data/gemfiles/activesupport_3.gemfile +8 -0
  10. data/gemfiles/activesupport_3.gemfile.lock +63 -0
  11. data/gemfiles/activesupport_4.gemfile +8 -0
  12. data/gemfiles/activesupport_4.gemfile.lock +71 -0
  13. data/lib/light-service.rb +1 -1
  14. data/lib/light-service/action.rb +6 -8
  15. data/lib/light-service/configuration.rb +0 -2
  16. data/lib/light-service/context.rb +32 -22
  17. data/lib/light-service/context/key_verifier.rb +87 -83
  18. data/lib/light-service/localization_adapter.rb +10 -7
  19. data/lib/light-service/organizer.rb +6 -3
  20. data/lib/light-service/organizer/with_reducer.rb +53 -39
  21. data/lib/light-service/organizer/with_reducer_factory.rb +3 -4
  22. data/lib/light-service/organizer/with_reducer_log_decorator.rb +81 -51
  23. data/lib/light-service/version.rb +2 -1
  24. data/light-service.gemspec +4 -4
  25. data/resources/fail_actions.png +0 -0
  26. data/resources/skip_actions.png +0 -0
  27. data/spec/acceptance/around_each_spec.rb +27 -0
  28. data/spec/acceptance/include_warning_spec.rb +6 -2
  29. data/spec/acceptance/log_from_organizer_spec.rb +39 -18
  30. data/spec/acceptance/message_localization_spec.rb +23 -23
  31. data/spec/acceptance/rollback_spec.rb +1 -3
  32. data/spec/action_expected_keys_spec.rb +32 -19
  33. data/spec/action_promised_keys_spec.rb +72 -54
  34. data/spec/action_spec.rb +23 -5
  35. data/spec/context_spec.rb +21 -17
  36. data/spec/localization_adapter_spec.rb +14 -10
  37. data/spec/organizer/with_reducer_spec.rb +19 -2
  38. data/spec/organizer_key_aliases_spec.rb +6 -5
  39. data/spec/organizer_spec.rb +32 -56
  40. data/spec/sample/calculates_tax_spec.rb +17 -9
  41. data/spec/sample/provides_free_shipping_action_spec.rb +3 -7
  42. data/spec/sample/tax/calculates_order_tax_action.rb +3 -2
  43. data/spec/sample/tax/calculates_tax.rb +3 -4
  44. data/spec/sample/tax/looks_up_tax_percentage_action.rb +10 -8
  45. data/spec/sample/tax/provides_free_shipping_action.rb +2 -4
  46. data/spec/spec_helper.rb +0 -1
  47. data/spec/test_doubles.rb +38 -15
  48. 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
- .with(message_or_key, :scope => expected_scope)
19
- .and_return("message")
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
- .with(message_or_key, hash_including(:i18n_variable => "value"))
27
- .and_return("message")
26
+ .with(message_or_key, hash_including(:i18n_variable => "value"))
27
+ .and_return("message")
28
28
 
29
- subject = adapter.failure(message_or_key, action_class, :i18n_variable => "value")
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
- .with(message_or_key, :scope => expected_scope)
55
- .and_return("message")
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
- .with(message_or_key, hash_including(:i18n_variable => "value"))
63
- .and_return("message")
64
+ .with(message_or_key, hash_including(:i18n_variable => "value"))
65
+ .and_return("message")
64
66
 
65
- subject = adapter.success(message_or_key, action_class, :i18n_variable => "value")
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) { raise LightService::FailWithRollbackError }
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) { raise LightService::FailWithRollbackError }
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
- TestDoubles::PromisesPromisedKeyAction,
14
- TestDoubles::ExpectsExpectedKeyAction,
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
@@ -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
- .with(context) \
13
- .and_return context
14
- expect(TestDoubles::AnotherAction).to receive(:execute) \
15
- .with(context) \
16
- .and_return context
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(context)
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
- .with(context) \
29
- .and_return context
30
- expect(TestDoubles::AnotherAction).to receive(:execute) \
31
- .with(context) \
32
- .and_return context
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(context)
37
- expect(result).to eq(context)
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(context) }.to \
44
- raise_error RuntimeError, "No action(s) were provided"
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(context)
53
- expect(TestDoubles::AnotherAction).to receive(:execute) \
54
- .with(context) \
55
- .and_return(context)
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: true)
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
- receive(:with).
103
- with(hash_including(:_aliases => { :foo => :bar })).
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(:context) { double('context', :keys => [:user],
10
- :failure? => false, :skip_all? => false) }
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(::LightService::Context).to receive(:make) \
14
- .with(:order => order) \
15
- .and_return context
15
+ allow(LightService::Context).to receive(:make)
16
+ .with(:order => order)
17
+ .and_return(ctx)
16
18
 
17
- allow(LooksUpTaxPercentageAction).to receive(:execute).with(context).and_return context
18
- allow(CalculatesOrderTaxAction).to receive(:execute).with(context).and_return context
19
- allow(ProvidesFreeShippingAction).to receive(:execute).with(context).and_return context
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 context
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(:context) do
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(context)
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(context)
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 |context|
6
- context.order.tax = (context.order.total * (context.tax_percentage/100)).round(2)
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
- LooksUpTaxPercentageAction,
7
- CalculatesOrderTaxAction,
8
- ProvidesFreeShippingAction
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 |context|
7
- tax_ranges = TaxRange.for_region(context.order.region)
8
- context.tax_percentage = 0
6
+ executed do |ctx|
7
+ tax_ranges = TaxRange.for_region(ctx.order.region)
8
+ ctx.tax_percentage = 0
9
9
 
10
- next context if object_is_nil?(tax_ranges, context, 'The tax ranges were not found')
10
+ next ctx if object_is_nil?(tax_ranges, ctx, 'The tax ranges were not found')
11
11
 
12
- context.tax_percentage = tax_ranges.for_total(context.order.total)
12
+ ctx.tax_percentage = tax_ranges.for_total(ctx.order.total)
13
13
 
14
- next context if object_is_nil?(context.tax_percentage, context, 'The tax percentage was not found')
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, context, message)
18
+ def self.object_is_nil?(object, ctx, message)
18
19
  if object.nil?
19
- context.fail!(message)
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
@@ -4,9 +4,7 @@ class ProvidesFreeShippingAction
4
4
 
5
5
  executed do |context|
6
6
  order = context.order
7
- if order.total_with_tax > 200
8
- order.provide_free_shipping!
9
- end
10
- end
11
7
 
8
+ order.provide_free_shipping! if order.total_with_tax > 200
9
+ end
12
10
  end
@@ -3,7 +3,6 @@ $LOAD_PATH << File.join(File.dirname(__FILE__))
3
3
 
4
4
  require 'light-service'
5
5
  require 'ostruct'
6
- require 'rspec/its'
7
6
  require 'active_support/core_ext/string'
8
7
  require 'pry'
9
8
 
@@ -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} - with #{context.chocolate}"
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
- context.fail_with_rollback!("Can't make a latte from a milk that's super hot!")
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
- .reduce(TestDoubles::MakesTeaWithMilkAction,
102
- TestDoubles::MakesLatteAction)
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
- .reduce(TestDoubles::AddsTwoActionWithFetch,
112
- TestDoubles::MakesLatteAction)
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
- .reduce(TestDoubles::MakesLatteAction,
122
- TestDoubles::AddsTwoActionWithFetch)
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
- .reduce(TestDoubles::MakesLatteAction,
133
- TestDoubles::AddsTwoActionWithFetch)
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