functional-light-service 0.4.4 → 6.0.0

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/project-build.yml +43 -11
  3. data/.rubocop.yml +101 -160
  4. data/AUDIT-functional-light-service.md +352 -0
  5. data/Appraisals +4 -0
  6. data/CHANGELOG.md +118 -0
  7. data/Gemfile +0 -2
  8. data/README.md +1544 -1426
  9. data/Rakefile +1 -1
  10. data/VERSION +1 -1
  11. data/audit/bench.rb +99 -0
  12. data/audit/verify_findings.rb +172 -0
  13. data/functional-light-service.gemspec +15 -16
  14. data/lib/functional-light-service/action.rb +97 -101
  15. data/lib/functional-light-service/configuration.rb +26 -24
  16. data/lib/functional-light-service/context/key_verifier.rb +124 -118
  17. data/lib/functional-light-service/context.rb +63 -20
  18. data/lib/functional-light-service/deprecations.rb +26 -0
  19. data/lib/functional-light-service/errors.rb +8 -6
  20. data/lib/functional-light-service/functional/enum.rb +286 -250
  21. data/lib/functional-light-service/functional/maybe.rb +21 -15
  22. data/lib/functional-light-service/functional/monad.rb +77 -66
  23. data/lib/functional-light-service/functional/null.rb +88 -74
  24. data/lib/functional-light-service/functional/option.rb +100 -97
  25. data/lib/functional-light-service/functional/result.rb +129 -116
  26. data/lib/functional-light-service/localization_adapter.rb +48 -47
  27. data/lib/functional-light-service/organizer/execute.rb +16 -14
  28. data/lib/functional-light-service/organizer/iterate.rb +30 -25
  29. data/lib/functional-light-service/organizer/reduce_if.rb +19 -17
  30. data/lib/functional-light-service/organizer/reduce_until.rb +22 -20
  31. data/lib/functional-light-service/organizer/scoped_reducable.rb +15 -13
  32. data/lib/functional-light-service/organizer/with_callback.rb +28 -26
  33. data/lib/functional-light-service/organizer/with_reducer.rb +81 -71
  34. data/lib/functional-light-service/organizer/with_reducer_factory.rb +20 -18
  35. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +110 -105
  36. data/lib/functional-light-service/organizer.rb +114 -104
  37. data/lib/functional-light-service/testing/context_factory.rb +48 -42
  38. data/lib/functional-light-service/testing.rb +3 -1
  39. data/lib/functional-light-service/version.rb +5 -3
  40. data/lib/functional-light-service.rb +30 -28
  41. data/spec/acceptance/after_actions_spec.rb +87 -71
  42. data/spec/acceptance/before_actions_spec.rb +115 -98
  43. data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -60
  44. data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
  45. data/spec/acceptance/fail_spec.rb +52 -50
  46. data/spec/acceptance/message_localization_spec.rb +119 -118
  47. data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
  48. data/spec/acceptance/organizer/add_to_context_spec.rb +30 -0
  49. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +68 -65
  50. data/spec/acceptance/organizer/iterate_spec.rb +7 -0
  51. data/spec/acceptance/organizer/reduce_if_spec.rb +89 -83
  52. data/spec/acceptance/organizer/reduce_until_spec.rb +6 -0
  53. data/spec/acceptance/organizer/with_callback_spec.rb +113 -110
  54. data/spec/acceptance/{not_having_call_method_warning_spec.rb → organizer_entry_point_spec.rb} +10 -7
  55. data/spec/acceptance/rollback_spec.rb +183 -132
  56. data/spec/action_expects_and_promises_spec.rb +97 -93
  57. data/spec/action_promised_keys_spec.rb +126 -122
  58. data/spec/action_spec.rb +8 -0
  59. data/spec/context_spec.rb +289 -197
  60. data/spec/examples/controller_spec.rb +63 -63
  61. data/spec/examples/validate_address_spec.rb +38 -37
  62. data/spec/lib/deterministic/currify_spec.rb +90 -88
  63. data/spec/lib/deterministic/null_spec.rb +6 -1
  64. data/spec/lib/deterministic/option_spec.rb +140 -133
  65. data/spec/lib/deterministic/result/result_map_spec.rb +155 -154
  66. data/spec/lib/deterministic/result/result_shared.rb +3 -2
  67. data/spec/lib/deterministic/result_spec.rb +2 -2
  68. data/spec/lib/edge_cases_spec.rb +156 -0
  69. data/spec/lib/enum_spec.rb +1 -1
  70. data/spec/lib/native_pattern_matching_spec.rb +74 -0
  71. data/spec/organizer_spec.rb +115 -93
  72. data/spec/readme_spec.rb +45 -47
  73. data/spec/sample/calculates_order_tax_action_spec.rb +16 -16
  74. data/spec/sample/calculates_tax_spec.rb +1 -1
  75. data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -55
  76. data/spec/sample/provides_free_shipping_action_spec.rb +1 -1
  77. data/spec/sample/tax/calculates_order_tax_action.rb +10 -9
  78. data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -27
  79. data/spec/sample/tax/provides_free_shipping_action.rb +11 -10
  80. data/spec/spec_helper.rb +21 -13
  81. data/spec/test_doubles.rb +628 -564
  82. data/spec/testing/context_factory_spec.rb +21 -0
  83. metadata +49 -117
  84. data/.travis.yml +0 -24
  85. data/lib/functional-light-service/organizer/verify_call_method_exists.rb +0 -29
  86. data/spec/acceptance/include_warning_spec.rb +0 -29
@@ -1,37 +1,38 @@
1
- require 'spec_helper'
2
-
3
- # A Unit of Work for validating an address
4
- module ValidateAddress
5
- extend FunctionalLightService::Prelude::Result
6
-
7
- def self.call(candidate)
8
- errors = {}
9
- errors[:street] = "Street cannot be empty" unless candidate.key? :street
10
- errors[:city] = "Street cannot be empty" unless candidate.key? :city
11
- errors[:postal] = "Street cannot be empty" unless candidate.key? :postal
12
-
13
- errors.empty? ? Success(candidate) : Failure(errors)
14
- end
15
- end
16
-
17
- describe ValidateAddress do
18
- include FunctionalLightService
19
- subject { ValidateAddress.call(candidate) }
20
- context 'sunny day' do
21
- let(:candidate) do
22
- { :title => "Hobbiton",
23
- :street => "501 Buckland Rd",
24
- :city => "Matamata",
25
- :postal => "3472",
26
- :country => "nz" }
27
- end
28
- specify { expect(subject).to be_a FunctionalLightService::Result::Success }
29
- specify { expect(subject.value).to eq candidate }
30
- end
31
-
32
- context 'empty data' do
33
- let(:candidate) { {} }
34
- specify { expect(subject).to be_a FunctionalLightService::Result::Failure }
35
- specify { expect(subject.value).to include(:street, :city, :postal) }
36
- end
37
- end
1
+ require 'spec_helper'
2
+
3
+ # A Unit of Work for validating an address
4
+ module ValidateAddress
5
+ extend FunctionalLightService::Prelude::Result
6
+
7
+ def self.call(candidate)
8
+ errors = {}
9
+ errors[:street] = "Street cannot be empty" unless candidate.key? :street
10
+ errors[:city] = "Street cannot be empty" unless candidate.key? :city
11
+ errors[:postal] = "Street cannot be empty" unless candidate.key? :postal
12
+
13
+ errors.empty? ? Success(candidate) : Failure(errors)
14
+ end
15
+ end
16
+
17
+ describe ValidateAddress do
18
+ include FunctionalLightService
19
+
20
+ subject { ValidateAddress.call(candidate) }
21
+ context 'sunny day' do
22
+ let(:candidate) do
23
+ { :title => "Hobbiton",
24
+ :street => "501 Buckland Rd",
25
+ :city => "Matamata",
26
+ :postal => "3472",
27
+ :country => "nz" }
28
+ end
29
+ specify { expect(subject).to be_a FunctionalLightService::Result::Success }
30
+ specify { expect(subject.value).to eq candidate }
31
+ end
32
+
33
+ context 'empty data' do
34
+ let(:candidate) { {} }
35
+ specify { expect(subject).to be_a FunctionalLightService::Result::Failure }
36
+ specify { expect(subject.value).to include(:street, :city, :postal) }
37
+ end
38
+ end
@@ -1,88 +1,90 @@
1
- require 'spec_helper'
2
- require 'pry'
3
-
4
- module FunctionalLightService
5
- module Currify
6
- module ClassMethods
7
- def currify(*names)
8
- names.each do |name|
9
- unbound_method = instance_method(name)
10
-
11
- define_method(name) do |*args|
12
- curried_method = unbound_method.bind(self).to_proc.curry
13
- curried_method[*args]
14
- end
15
- end
16
- end
17
- end
18
-
19
- def self.included(curried)
20
- curried.extend ClassMethods
21
- end
22
- end
23
- end
24
-
25
- class Object
26
- class Proc
27
- def self.compose(f, g)
28
- ->(*args) { f[g[*args]] }
29
- end
30
-
31
- # rubocop:disable Naming/BinaryOperatorParameterName
32
- # Compose left to right
33
- def |(g)
34
- Proc.compose(g, self)
35
- end
36
-
37
- # Compose right to left
38
- def *(g)
39
- Proc.compose(self, g)
40
- end
41
- # rubocop:enable Naming/BinaryOperatorParameterName
42
- end
43
- end
44
-
45
- class Booking
46
- include FunctionalLightService::Currify
47
- include FunctionalLightService::Prelude::Result
48
-
49
- def initialize(deps)
50
- @deps = deps
51
- end
52
-
53
- def build(id, format)
54
- validate(id) | req | find | render(format)
55
-
56
- validate(id) | request(id) | find
57
- end
58
-
59
- def validate(id)
60
- Success(id)
61
- end
62
-
63
- def req(a, id)
64
- Success(:id => id + a)
65
- end
66
-
67
- def find(req)
68
- Success(:found => req)
69
- end
70
-
71
- def render(format, req)
72
- Success("rendered in #{format}: #{req[:found]}")
73
- end
74
-
75
- currify :find, :render, :req
76
- end
77
-
78
- describe "Pref" do
79
- include FunctionalLightService::Prelude::Result
80
-
81
- it "does something" do
82
- b = Booking.new(1)
83
- actual = b.validate(1) >> b.req(2) >> b.find >> b.render(:html)
84
-
85
- expected = FunctionalLightService::Result::Success.new("rendered in html: {:id=>3}")
86
- expect(actual).to eq expected
87
- end
88
- end
1
+ require 'spec_helper'
2
+ require 'pry'
3
+
4
+ module FunctionalLightService
5
+ module Currify
6
+ module ClassMethods
7
+ def currify(*names)
8
+ names.each do |name|
9
+ unbound_method = instance_method(name)
10
+
11
+ define_method(name) do |*args|
12
+ curried_method = unbound_method.bind(self).to_proc.curry
13
+ curried_method[*args]
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ def self.included(curried)
20
+ curried.extend ClassMethods
21
+ end
22
+ end
23
+ end
24
+
25
+ class Object
26
+ class Proc
27
+ def self.compose(f, g)
28
+ ->(*args) { f[g[*args]] }
29
+ end
30
+
31
+ # rubocop:disable Naming/BinaryOperatorParameterName
32
+ # Compose left to right
33
+ def |(g)
34
+ Proc.compose(g, self)
35
+ end
36
+
37
+ # Compose right to left
38
+ def *(g)
39
+ Proc.compose(self, g)
40
+ end
41
+ # rubocop:enable Naming/BinaryOperatorParameterName
42
+ end
43
+ end
44
+
45
+ class Booking
46
+ include FunctionalLightService::Currify
47
+ include FunctionalLightService::Prelude::Result
48
+
49
+ def initialize(deps)
50
+ @deps = deps
51
+ end
52
+
53
+ def build(id, format)
54
+ validate(id) | req | find | render(format)
55
+
56
+ validate(id) | request(id) | find
57
+ end
58
+
59
+ def validate(id)
60
+ Success(id)
61
+ end
62
+
63
+ def req(a, id)
64
+ Success(:id => id + a)
65
+ end
66
+
67
+ def find(req)
68
+ Success(:found => req)
69
+ end
70
+
71
+ def render(format, req)
72
+ Success("rendered in #{format}: #{req[:found]}")
73
+ end
74
+
75
+ currify :find, :render, :req
76
+ end
77
+
78
+ describe "Pref" do
79
+ include FunctionalLightService::Prelude::Result
80
+
81
+ it "does something" do
82
+ b = Booking.new(1)
83
+ actual = b.validate(1) >> b.req(2) >> b.find >> b.render(:html)
84
+
85
+ # il formato di Hash#to_s cambia tra Ruby 3.3 e 3.4: costruiamo l'atteso dinamicamente
86
+ rendered_params = { :id => 3 }
87
+ expected = FunctionalLightService::Result::Success.new("rendered in html: #{rendered_params}")
88
+ expect(actual).to eq expected
89
+ end
90
+ end
@@ -3,7 +3,12 @@ require "spec_helper"
3
3
  describe Null do
4
4
  it "Null is a Singleton" do
5
5
  expect(Null.instance).to be_a Null
6
- expect { Null.new }.to raise_error(NoMethodError, "private method `new' called for Null:Class")
6
+ expect { Null.new }.to raise_error(NoMethodError, /private method [`']new'/)
7
+ end
8
+
9
+ it "respond_to? accepts the standard two-argument form" do
10
+ expect(Null.instance.respond_to?(:anything, true)).to be(true)
11
+ expect(Null.instance.respond_to?(:anything)).to be(true)
7
12
  end
8
13
 
9
14
  it "explicit conversions" do
@@ -1,133 +1,140 @@
1
- require 'spec_helper'
2
-
3
- # rubocop:disable Style/MixinUsage
4
- include FunctionalLightService::Prelude::Option
5
- # rubocop:enable Style/MixinUsage
6
-
7
- describe FunctionalLightService::Option do
8
- specify { expect(described_class::Some.new(0)).to be_a described_class::Some }
9
- specify { expect(described_class::Some.new(0)).to be_a described_class }
10
- specify { expect(described_class::Some.new(0)).to eq Some(0) }
11
-
12
- specify { expect(described_class::None.new).to eq described_class::None.new }
13
- specify { expect(described_class::None.new).to be_a described_class::None }
14
- specify { expect(described_class::None.new).to be_a described_class }
15
- specify { expect(described_class::None.new).to eq None }
16
-
17
- it "join" do
18
- expect(Some(Some(1))).to eq Some(1)
19
- end
20
-
21
- it "fmap" do
22
- expect(Some(1).fmap { |n| n + 1 }).to eq Some(2)
23
- expect(None.fmap { |n| n + 1 }).to eq None
24
- end
25
-
26
- it "map" do
27
- expect(Some(1).map { |n| Some(n + 1) }).to eq Some(2)
28
- expect(Some(1).map { |_n| None }).to eq None
29
- expect(None.map { |n| Some(n + 1) }).to eq None
30
- end
31
-
32
- it "some?" do
33
- expect(Some(1).some?).to be_truthy
34
- expect(None.some?).to be_falsey
35
- end
36
-
37
- it "none?" do
38
- expect(None.none?).to be_truthy
39
- expect(Some(1).none?).to be_falsey
40
- end
41
-
42
- it "value" do
43
- expect(Some(1).value).to eq 1
44
- expect { None.value }.to raise_error NoMethodError
45
- end
46
-
47
- it "value_or" do
48
- expect(Some(1).value_or(2)).to eq 1
49
- expect(None.value_or(0)).to eq 0
50
- end
51
-
52
- it "+" do
53
- expect(Some([1]) + None).to eq Some([1])
54
- expect(Some(1) + None + None).to eq Some(1)
55
- expect(Some(1) + Some(1)).to eq Some(2)
56
- expect(None + Some(1)).to eq Some(1)
57
- expect(None + None + Some(1)).to eq Some(1)
58
- expect(None + None + Some(1) + None).to eq Some(1)
59
- expect(None + Some(:foo => 1)).to eq Some(:foo => 1)
60
- expect(Some([1]) + Some([1])).to eq Some([1, 1])
61
- expect { Some([1]) + Some(1) }.to raise_error TypeError
62
- end
63
-
64
- it "inspect" do
65
- expect(Some(1).inspect).to eq "Some(1)"
66
- expect(described_class::None.new.inspect).to eq "None"
67
- end
68
-
69
- it "to_s" do
70
- expect(Some(1).to_s).to eq "1"
71
- expect(described_class::None.new.to_s).to eq ""
72
- end
73
-
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
82
-
83
- expect(
84
- Some(1).match do
85
- None() { 0 }
86
- Some() { |_s| 1 }
87
- end
88
- ).to eq 1
89
-
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
96
-
97
- expect(
98
- None.match do
99
- None() { 0 }
100
- Some() { 1 }
101
- end
102
- ).to eq 0
103
- end
104
-
105
- it "nil?" do
106
- expect(described_class.some?(nil)).to eq None
107
- expect(described_class.some?(1)).to be_some
108
- expect(described_class.some?(1)).to eq Some(1)
109
- end
110
-
111
- it "any?" do
112
- expect(described_class.any?(nil)).to be_none
113
- expect(described_class.any?(None)).to be_none
114
- expect(described_class.any?("")).to be_none
115
- expect(described_class.any?([])).to be_none
116
- expect(described_class.any?({})).to be_none
117
- expect(described_class.any?([1])).to eq Some([1])
118
- expect(described_class.any?(:foo => 1)).to eq Some(:foo => 1)
119
- expect(described_class.any?(1)).to eq Some(1)
120
- end
121
-
122
- it "try!" do
123
- expect(described_class.try! { raise "error" }).to be_none
124
- end
125
- end
126
-
127
- require_relative 'monad_axioms'
128
-
129
- describe FunctionalLightService::Option::Some do
130
- it_behaves_like 'a Monad' do
131
- let(:monad) { described_class }
132
- end
133
- end
1
+ require 'spec_helper'
2
+
3
+ include FunctionalLightService::Prelude::Option
4
+
5
+ describe FunctionalLightService::Option do
6
+ it "Some cannot wrap nil" do
7
+ expect { described_class::Some.new(nil) }
8
+ .to raise_error(ArgumentError, /use None instead/)
9
+ end
10
+
11
+ specify { expect(described_class::Some.new(0)).to be_a described_class::Some }
12
+ specify { expect(described_class::Some.new(0)).to be_a described_class }
13
+ specify { expect(described_class::Some.new(0)).to eq Some(0) }
14
+
15
+ specify { expect(described_class::None.new).to eq described_class::None.new }
16
+ specify { expect(described_class::None.new).to be_a described_class::None }
17
+ specify { expect(described_class::None.new).to be_a described_class }
18
+ specify { expect(described_class::None.new).to eq None }
19
+
20
+ it "join" do
21
+ expect(Some(Some(1))).to eq Some(1)
22
+ end
23
+
24
+ it "fmap" do
25
+ expect(Some(1).fmap { |n| n + 1 }).to eq Some(2)
26
+ expect(None.fmap { |n| n + 1 }).to eq None
27
+ end
28
+
29
+ it "map" do
30
+ expect(Some(1).map { |n| Some(n + 1) }).to eq Some(2)
31
+ expect(Some(1).map { |_n| None }).to eq None
32
+ expect(None.map { |n| Some(n + 1) }).to eq None
33
+ end
34
+
35
+ it "some?" do
36
+ expect(Some(1).some?).to be_truthy
37
+ expect(None.some?).to be_falsey
38
+ end
39
+
40
+ it "none?" do
41
+ expect(None.none?).to be_truthy
42
+ expect(Some(1).none?).to be_falsey
43
+ end
44
+
45
+ it "value" do
46
+ expect(Some(1).value).to eq 1
47
+ expect { None.value }.to raise_error NoMethodError
48
+ end
49
+
50
+ it "value_or" do
51
+ expect(Some(1).value_or(2)).to eq 1
52
+ expect(None.value_or(0)).to eq 0
53
+ end
54
+
55
+ it "+" do
56
+ expect(Some([1]) + None).to eq Some([1])
57
+ expect(Some(1) + None + None).to eq Some(1)
58
+ expect(Some(1) + Some(1)).to eq Some(2)
59
+ expect(None + Some(1)).to eq Some(1)
60
+ expect(None + None + Some(1)).to eq Some(1)
61
+ expect(None + None + Some(1) + None).to eq Some(1)
62
+ expect(None + Some(:foo => 1)).to eq Some(:foo => 1)
63
+ expect(Some([1]) + Some([1])).to eq Some([1, 1])
64
+ expect { Some([1]) + Some(1) }.to raise_error TypeError
65
+ end
66
+
67
+ it "inspect" do
68
+ expect(Some(1).inspect).to eq "Some(1)"
69
+ expect(described_class::None.new.inspect).to eq "None"
70
+ end
71
+
72
+ it "to_s" do
73
+ expect(Some(1).to_s).to eq "1"
74
+ expect(described_class::None.new.to_s).to eq ""
75
+ end
76
+
77
+ it "match" do
78
+ expect(
79
+ # rubocop:disable Lint/UnusedBlockArgument
80
+ # rubocop:disable Lint/EmptyBlock
81
+ Some(0).match do
82
+ Some(where { s == 1 }) { |s| 99 }
83
+ Some(where { s == 0 }) { |s| s + 1 }
84
+ None() {}
85
+ end
86
+ ).to eq 1
87
+
88
+ expect(
89
+ Some(1).match do
90
+ None() { 0 }
91
+ Some() { |s| 1 }
92
+ end
93
+ ).to eq 1
94
+
95
+ expect(
96
+ Some(1).match do
97
+ None() { 0 }
98
+ Some(where { s.is_a? Integer }) { |s| 1 }
99
+ end
100
+ # rubocop:enable Lint/UnusedBlockArgument
101
+ # rubocop:enable Lint/EmptyBlock
102
+ ).to eq 1
103
+
104
+ expect(
105
+ None.match do
106
+ None() { 0 }
107
+ Some() { 1 }
108
+ end
109
+ ).to eq 0
110
+ end
111
+
112
+ it "nil?" do
113
+ expect(described_class.some?(nil)).to eq None
114
+ expect(described_class.some?(1)).to be_some
115
+ expect(described_class.some?(1)).to eq Some(1)
116
+ end
117
+
118
+ it "any?" do
119
+ expect(described_class.any?(nil)).to be_none
120
+ expect(described_class.any?(None)).to be_none
121
+ expect(described_class.any?("")).to be_none
122
+ expect(described_class.any?([])).to be_none
123
+ expect(described_class.any?({})).to be_none
124
+ expect(described_class.any?([1])).to eq Some([1])
125
+ expect(described_class.any?(:foo => 1)).to eq Some(:foo => 1)
126
+ expect(described_class.any?(1)).to eq Some(1)
127
+ end
128
+
129
+ it "try!" do
130
+ expect(described_class.try! { raise "error" }).to be_none
131
+ end
132
+ end
133
+
134
+ require_relative 'monad_axioms'
135
+
136
+ describe FunctionalLightService::Option::Some do
137
+ it_behaves_like 'a Monad' do
138
+ let(:monad) { described_class }
139
+ end
140
+ end