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.
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