functional-light-service 0.3.4 → 0.5.4

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/project-build.yml +47 -0
  3. data/.rubocop.yml +103 -15
  4. data/.solargraph.yml +11 -0
  5. data/Appraisals +6 -2
  6. data/CHANGELOG.md +128 -0
  7. data/Gemfile +1 -3
  8. data/README.md +1492 -1424
  9. data/Rakefile +1 -1
  10. data/VERSION +1 -1
  11. data/functional-light-service.gemspec +19 -8
  12. data/gemfiles/dry_inflector_0_2_1.gemfile +5 -0
  13. data/gemfiles/i18n_1_8_11.gemfile +5 -0
  14. data/lib/functional-light-service/context/key_verifier.rb +2 -2
  15. data/lib/functional-light-service/context.rb +152 -154
  16. data/lib/functional-light-service/functional/enum.rb +2 -6
  17. data/lib/functional-light-service/functional/maybe.rb +1 -0
  18. data/lib/functional-light-service/functional/null.rb +1 -1
  19. data/lib/functional-light-service/functional/option.rb +0 -2
  20. data/lib/functional-light-service/functional/result.rb +2 -2
  21. data/lib/functional-light-service/organizer/with_reducer.rb +6 -0
  22. data/lib/functional-light-service/organizer/with_reducer_factory.rb +1 -1
  23. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +5 -2
  24. data/lib/functional-light-service/organizer.rb +10 -0
  25. data/lib/functional-light-service/testing/context_factory.rb +2 -0
  26. data/lib/functional-light-service/version.rb +1 -1
  27. data/spec/acceptance/fail_spec.rb +42 -16
  28. data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
  29. data/spec/acceptance/organizer/add_to_context_spec.rb +30 -0
  30. data/spec/acceptance/organizer/iterate_spec.rb +7 -0
  31. data/spec/acceptance/organizer/reduce_if_spec.rb +38 -0
  32. data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
  33. data/spec/action_spec.rb +8 -0
  34. data/spec/context/inspect_spec.rb +6 -21
  35. data/spec/context_spec.rb +1 -1
  36. data/spec/lib/deterministic/monad_axioms.rb +2 -0
  37. data/spec/lib/deterministic/monad_spec.rb +2 -0
  38. data/spec/lib/deterministic/null_spec.rb +2 -0
  39. data/spec/lib/deterministic/option_spec.rb +18 -14
  40. data/spec/lib/enum_spec.rb +3 -1
  41. data/spec/organizer_spec.rb +21 -0
  42. data/spec/sample/looks_up_tax_percentage_action_spec.rb +3 -1
  43. data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
  44. data/spec/spec_helper.rb +8 -5
  45. data/spec/test_doubles.rb +56 -9
  46. metadata +156 -26
  47. data/.travis.yml +0 -24
  48. data/gemfiles/dry_inflector_0_2.gemfile +0 -8
  49. data/gemfiles/i18n_1_8.gemfile +0 -8
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe FunctionalLightService::Organizer do
4
+ class TestAddToContext
5
+ extend FunctionalLightService::Organizer
6
+
7
+ def self.call(context = FunctionalLightService::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
@@ -20,6 +20,13 @@ RSpec.describe FunctionalLightService::Organizer do
20
20
  expect(result.number).to eq(5)
21
21
  end
22
22
 
23
+ it "knows that it's being iterated from within an organizer" do
24
+ result = TestDoubles::TestIterate.call(:number => 1,
25
+ :counters => [1, 2, 3, 4])
26
+
27
+ expect(result.organized_by).to eq TestDoubles::TestIterate
28
+ end
29
+
23
30
  it 'will not iterate over a failed context' do
24
31
  empty_context.fail!('Something bad happened')
25
32
 
@@ -48,4 +48,42 @@ RSpec.describe FunctionalLightService::Organizer do
48
48
  result = TestReduceIf.call(empty_context)
49
49
  expect(result).to be_success
50
50
  end
51
+
52
+ it "knows that it's being conditionally reduced from within an organizer" do
53
+ result = TestReduceIf.call(:number => 2)
54
+
55
+ expect(result.organized_by).to eq TestReduceIf
56
+ end
57
+
58
+ it 'skips actions within in its own scope' do
59
+ org = Class.new do
60
+ extend FunctionalLightService::Organizer
61
+
62
+ def self.call
63
+ reduce(actions)
64
+ end
65
+
66
+ def self.actions
67
+ [
68
+ reduce_if(
69
+ ->(c) { !c.nil? },
70
+ [
71
+ execute(->(c) { c[:first_reduce_if] = true }),
72
+ execute(->(c) { c.skip_remaining! }),
73
+ execute(->(c) { c[:second_reduce_if] = true })
74
+ ]
75
+ ),
76
+ execute(->(c) { c[:last_outside] = true })
77
+ ]
78
+ end
79
+ end
80
+
81
+ result = org.call
82
+
83
+ aggregate_failures do
84
+ expect(result[:first_reduce_if]).to be true
85
+ expect(result[:second_reduce_if]).to be_nil
86
+ expect(result[:last_outside]).to be true
87
+ end
88
+ end
51
89
  end
@@ -40,4 +40,10 @@ RSpec.describe FunctionalLightService::Organizer do
40
40
  result = TestReduceUntil.call(empty_context)
41
41
  expect(result).to be_success
42
42
  end
43
+
44
+ it "is expected to know its organizer when reducing until a condition" do
45
+ result = TestReduceUntil.call(:number => 1)
46
+
47
+ expect(result.organized_by).to eq TestReduceUntil
48
+ end
43
49
  end
data/spec/action_spec.rb CHANGED
@@ -68,6 +68,14 @@ describe FunctionalLightService::Action do
68
68
  expect(result.to_hash).to eq(:number => 2)
69
69
  end
70
70
 
71
+ context "when called directly" do
72
+ it "is expected to not be organized" do
73
+ result = TestDoubles::AddsTwoActionWithFetch.execute(context)
74
+
75
+ expect(result.organized_by).to be_nil
76
+ end
77
+ end
78
+
71
79
  context "when invoked with hash" do
72
80
  it "creates FunctionalLightService::Context implicitly" do
73
81
  ctx = { :some_key => "some value" }
@@ -13,13 +13,8 @@ RSpec.describe FunctionalLightService::Context do
13
13
  describe '#inspect' do
14
14
  it 'inspects the hash with all the fields' do
15
15
  inspected_context =
16
- 'FunctionalLightService::Context({}, ' \
17
- + 'success: true, ' \
18
- + 'message: \'\', ' \
19
- + 'error_code: nil, ' \
20
- + 'skip_remaining: false, ' \
21
- + 'aliases: {}' \
22
- + ')'
16
+ "FunctionalLightService::Context({}, success: true, message: '', error_code: nil, " \
17
+ "skip_remaining: false, aliases: {})"
23
18
 
24
19
  expect(context.inspect).to eq(inspected_context)
25
20
  end
@@ -28,13 +23,8 @@ RSpec.describe FunctionalLightService::Context do
28
23
  context.fail!('There was an error')
29
24
 
30
25
  inspected_context =
31
- 'FunctionalLightService::Context({}, ' \
32
- + 'success: false, ' \
33
- + 'message: \'There was an error\', ' \
34
- + 'error_code: nil, ' \
35
- + 'skip_remaining: false, ' \
36
- + 'aliases: {}' \
37
- + ')'
26
+ "FunctionalLightService::Context({}, success: false, message: 'There was an error', " \
27
+ "error_code: nil, skip_remaining: false, aliases: {})"
38
28
 
39
29
  expect(context.inspect).to eq(inspected_context)
40
30
  end
@@ -43,13 +33,8 @@ RSpec.describe FunctionalLightService::Context do
43
33
  context.skip_remaining!('No need to process')
44
34
 
45
35
  inspected_context =
46
- 'FunctionalLightService::Context({}, ' \
47
- + 'success: true, ' \
48
- + 'message: \'No need to process\', ' \
49
- + 'error_code: nil, ' \
50
- + 'skip_remaining: true, ' \
51
- + 'aliases: {}' \
52
- + ')'
36
+ "FunctionalLightService::Context({}, success: true, message: 'No need to process', " \
37
+ "error_code: nil, skip_remaining: true, aliases: {})"
53
38
 
54
39
  expect(context.inspect).to eq(inspected_context)
55
40
  end
data/spec/context_spec.rb CHANGED
@@ -167,7 +167,7 @@ RSpec.describe FunctionalLightService::Context do
167
167
  end
168
168
 
169
169
  it "allows a default block value for #fetch" do
170
- expect(context.fetch(:madeup) { :default }).to eq(:default)
170
+ expect(context.fetch(:madeup, :default)).to eq(:default)
171
171
  end
172
172
 
173
173
  context "when aliases are included via .make" do
@@ -34,7 +34,9 @@ shared_examples 'a Monad' do
34
34
 
35
35
  it '#bind must return a monad' do
36
36
  expect(monad.new(1).bind { |v| monad.new(v) }).to eq monad.new(1)
37
+ # rubocop:disable Lint/EmptyBlock
37
38
  expect { monad.new(1).bind {} }.to raise_error(FunctionalLightService::Monad::NotMonadError)
39
+ # rubocop:enable Lint/EmptyBlock
38
40
  end
39
41
 
40
42
  it '#new must return a monad' do
@@ -21,8 +21,10 @@ describe FunctionalLightService::Monad do
21
21
 
22
22
  context '#bind' do
23
23
  it "raises an error if the passed function does not return a monad of the same class" do
24
+ # rubocop:disable Lint/EmptyBlock
24
25
  expect { Identity.new(1).bind {} }.to \
25
26
  raise_error(FunctionalLightService::Monad::NotMonadError)
27
+ # rubocop:enable Lint/EmptyBlock
26
28
  end
27
29
  specify { expect(Identity.new(1).bind { |value| Identity.new(value) }).to eq Identity.new(1) }
28
30
 
@@ -38,7 +38,9 @@ describe Null do
38
38
  null = Null.instance
39
39
  expect(null.to_str).to eq ""
40
40
  expect(null.to_ary).to eq []
41
+ # rubocop:disable Style/StringConcatenation
41
42
  expect("" + null).to eq ""
43
+ # rubocop:enable Style/StringConcatenation
42
44
 
43
45
  a, b, c = null
44
46
  expect(a).to be_nil
@@ -72,27 +72,31 @@ describe FunctionalLightService::Option do
72
72
  end
73
73
 
74
74
  it "match" do
75
- # expect(
76
- # Some(0).match do
77
- # Some(where { s == 1 }) { |_s| 99 }
78
- # Some(where { s == 0 }) { |s| s + 1 }
79
- # None() {}
80
- # end
81
- # ).to eq 1
75
+ expect(
76
+ # rubocop:disable Lint/UnusedBlockArgument
77
+ # rubocop:disable Lint/EmptyBlock
78
+ Some(0).match do
79
+ Some(where { s == 1 }) { |s| 99 }
80
+ Some(where { s == 0 }) { |s| s + 1 }
81
+ None() {}
82
+ end
83
+ ).to eq 1
82
84
 
83
85
  expect(
84
86
  Some(1).match do
85
87
  None() { 0 }
86
- Some() { |_s| 1 }
88
+ Some() { |s| 1 }
87
89
  end
88
90
  ).to eq 1
89
91
 
90
- # expect(
91
- # Some(1).match do
92
- # None() { 0 }
93
- # Some(where { s.is_a? Integer }) { |_s| 1 }
94
- # end
95
- # ).to eq 1
92
+ expect(
93
+ Some(1).match do
94
+ None() { 0 }
95
+ Some(where { s.is_a? Integer }) { |s| 1 }
96
+ end
97
+ # rubocop:enable Lint/UnusedBlockArgument
98
+ # rubocop:enable Lint/EmptyBlock
99
+ ).to eq 1
96
100
 
97
101
  expect(
98
102
  None.match do
@@ -8,7 +8,7 @@ describe FunctionalLightService::Enum do
8
8
  InvalidEnum = FunctionalLightService.enum do
9
9
  Unary(:value)
10
10
  end
11
- end .to raise_error ArgumentError
11
+ end.to raise_error ArgumentError
12
12
  end
13
13
 
14
14
  context "Nullary, Unary, Binary" do
@@ -32,7 +32,9 @@ describe FunctionalLightService::Enum do
32
32
  expect { n.value }.to raise_error NoMethodError
33
33
  expect(n.inspect).to eq "Nullary"
34
34
  expect(n.to_s).to eq ""
35
+ # rubocop:disable Lint/EmptyBlock
35
36
  expect(n.fmap {}).to eq n
37
+ # rubocop:enable Lint/EmptyBlock
36
38
  end
37
39
 
38
40
  it "Unary" do
@@ -19,6 +19,11 @@ describe FunctionalLightService::Organizer do
19
19
  result = TestDoubles::AnOrganizer.call(:user => user)
20
20
  expect(result).to eq(ctx)
21
21
  end
22
+
23
+ it "sets itself as the organizer" do
24
+ result = TestDoubles::AnOrganizer.call(:user => user)
25
+ expect(result.organized_by).to eq TestDoubles::AnOrganizer
26
+ end
22
27
  end
23
28
 
24
29
  context "when #with is called with Context" do
@@ -90,4 +95,20 @@ describe FunctionalLightService::Organizer do
90
95
  expect(reduced).to eq(ctx)
91
96
  end
92
97
  end
98
+
99
+ context "can add items to the context" do
100
+ specify 'with #add_to_context' do
101
+ result = TestDoubles::AnOrganizerThatAddsToContext.call
102
+ expect(result[:strongest_avenger]).to eq :thor
103
+ expect(result[:last_jedi]).to eq "Rey"
104
+ end
105
+ end
106
+
107
+ context "can assign key aliaeses" do
108
+ it 'with #add_aliases' do
109
+ result = TestDoubles::AnOrganizerThatAddsAliases.call
110
+ expect(result[:foo]).to eq :bar
111
+ expect(result[:baz]).to eq :bar
112
+ end
113
+ end
93
114
  end
@@ -1,7 +1,9 @@
1
1
  require 'spec_helper'
2
2
  require_relative 'tax/looks_up_tax_percentage_action'
3
3
 
4
- class TaxRange; end
4
+ class TaxRange
5
+ extend FunctionalLightService::Action
6
+ end
5
7
 
6
8
  describe LooksUpTaxPercentageAction do
7
9
  let(:region) { double('region') }
@@ -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
 
data/spec/spec_helper.rb CHANGED
@@ -10,11 +10,14 @@ if ENV['RUN_COVERAGE_REPORT']
10
10
  end
11
11
 
12
12
  SimpleCov.minimum_coverage_by_file 90
13
+
14
+ require "simplecov-cobertura"
15
+ SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
13
16
  end
14
17
 
15
- require 'functional-light-service'
16
- require 'functional-light-service/testing'
18
+ require "functional-light-service"
19
+ require "functional-light-service/testing"
17
20
  require "functional-light-service/functional/null"
18
- require 'support'
19
- require 'test_doubles'
20
- require 'stringio'
21
+ require "support"
22
+ require "test_doubles"
23
+ require "stringio"
data/spec/test_doubles.rb CHANGED
@@ -45,6 +45,7 @@ module TestDoubles
45
45
 
46
46
  class TestLogger
47
47
  attr_accessor :logs
48
+
48
49
  def initialize
49
50
  @logs = []
50
51
  end
@@ -83,8 +84,13 @@ module TestDoubles
83
84
  end
84
85
  end
85
86
 
86
- class AnAction; end
87
- class AnotherAction; end
87
+ class AnAction
88
+ extend FunctionalLightService::Action
89
+ end
90
+
91
+ class AnotherAction
92
+ extend FunctionalLightService::Action
93
+ end
88
94
 
89
95
  class AnOrganizer
90
96
  extend FunctionalLightService::Organizer
@@ -166,14 +172,18 @@ module TestDoubles
166
172
  promises :latte
167
173
 
168
174
  executed do |context|
175
+ context.fail!("Can't make a latte from a milk that's very hot!") if context.milk == :very_hot
176
+
177
+ if context.milk == :super_hot
178
+ error_message = "Can't make a latte from a milk that's super hot!"
179
+ context.fail_with_rollback!(error_message)
180
+ end
181
+
169
182
  context[:latte] = "#{context.coffee} - with lots of #{context.milk}"
170
- case context.milk
171
- when :very_hot then
172
- context.fail!("Can't make a latte from a milk that's very hot!")
173
- when :super_hot then
174
- context.fail_with_rollback!("Can't make a latte from a milk that's super hot!")
175
- when "5%" then
176
- context.skip_remaining!("Can't make a latte with a fatty milk like that!")
183
+
184
+ if context.milk == "5%"
185
+ msg = "Can't make a latte with a fatty milk like that!"
186
+ context.skip_remaining!(msg)
177
187
  next context
178
188
  end
179
189
  end
@@ -472,7 +482,9 @@ module TestDoubles
472
482
  class NullAction
473
483
  extend FunctionalLightService::Action
474
484
 
485
+ # rubocop:disable Lint/EmptyBlock
475
486
  executed { |_ctx| }
487
+ # rubocop:enable Lint/EmptyBlock
476
488
  end
477
489
 
478
490
  class TestIterate
@@ -549,4 +561,39 @@ module TestDoubles
549
561
  ctx.total += ctx.number
550
562
  end
551
563
  end
564
+
565
+ class CapitalizeMessage
566
+ extend FunctionalLightService::Action
567
+ expects :a_message
568
+ promises :final_message
569
+
570
+ executed do |ctx|
571
+ ctx.final_message = ctx.a_message.upcase
572
+ end
573
+ end
574
+
575
+ class AnOrganizerThatAddsToContext
576
+ extend FunctionalLightService::Organizer
577
+ def self.call
578
+ with.reduce(actions)
579
+ end
580
+
581
+ def self.actions
582
+ [add_to_context(
583
+ :strongest_avenger => :thor,
584
+ :last_jedi => "Rey"
585
+ )]
586
+ end
587
+ end
588
+
589
+ class AnOrganizerThatAddsAliases
590
+ extend FunctionalLightService::Organizer
591
+ def self.call
592
+ with(:foo => :bar).reduce(actions)
593
+ end
594
+
595
+ def self.actions
596
+ [add_aliases(:foo => :baz)]
597
+ end
598
+ end
552
599
  end