functional-light-service 0.2.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 (110) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +72 -0
  5. data/.travis.yml +22 -0
  6. data/Appraisals +3 -0
  7. data/CHANGELOG.md +37 -0
  8. data/CODE_OF_CONDUCT.md +22 -0
  9. data/Gemfile +6 -0
  10. data/LICENSE +22 -0
  11. data/README.md +1424 -0
  12. data/Rakefile +12 -0
  13. data/VERSION +1 -0
  14. data/functional-light-service.gemspec +26 -0
  15. data/gemfiles/activesupport_5.gemfile +8 -0
  16. data/gemfiles/activesupport_5.gemfile.lock +82 -0
  17. data/lib/functional-light-service/action.rb +102 -0
  18. data/lib/functional-light-service/configuration.rb +24 -0
  19. data/lib/functional-light-service/context/key_verifier.rb +118 -0
  20. data/lib/functional-light-service/context.rb +165 -0
  21. data/lib/functional-light-service/errors.rb +6 -0
  22. data/lib/functional-light-service/functional/enum.rb +254 -0
  23. data/lib/functional-light-service/functional/maybe.rb +14 -0
  24. data/lib/functional-light-service/functional/monad.rb +66 -0
  25. data/lib/functional-light-service/functional/null.rb +74 -0
  26. data/lib/functional-light-service/functional/option.rb +99 -0
  27. data/lib/functional-light-service/functional/result.rb +122 -0
  28. data/lib/functional-light-service/localization_adapter.rb +44 -0
  29. data/lib/functional-light-service/organizer/execute.rb +14 -0
  30. data/lib/functional-light-service/organizer/iterate.rb +22 -0
  31. data/lib/functional-light-service/organizer/reduce_if.rb +17 -0
  32. data/lib/functional-light-service/organizer/reduce_until.rb +20 -0
  33. data/lib/functional-light-service/organizer/scoped_reducable.rb +13 -0
  34. data/lib/functional-light-service/organizer/verify_call_method_exists.rb +28 -0
  35. data/lib/functional-light-service/organizer/with_callback.rb +26 -0
  36. data/lib/functional-light-service/organizer/with_reducer.rb +71 -0
  37. data/lib/functional-light-service/organizer/with_reducer_factory.rb +18 -0
  38. data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +105 -0
  39. data/lib/functional-light-service/organizer.rb +105 -0
  40. data/lib/functional-light-service/testing/context_factory.rb +40 -0
  41. data/lib/functional-light-service/testing.rb +1 -0
  42. data/lib/functional-light-service/version.rb +3 -0
  43. data/lib/functional-light-service.rb +29 -0
  44. data/resources/fail_actions.png +0 -0
  45. data/resources/light-service.png +0 -0
  46. data/resources/organizer_and_actions.png +0 -0
  47. data/resources/skip_actions.png +0 -0
  48. data/spec/acceptance/add_numbers_spec.rb +11 -0
  49. data/spec/acceptance/after_actions_spec.rb +71 -0
  50. data/spec/acceptance/around_each_spec.rb +19 -0
  51. data/spec/acceptance/before_actions_spec.rb +98 -0
  52. data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
  53. data/spec/acceptance/fail_spec.rb +24 -0
  54. data/spec/acceptance/include_warning_spec.rb +29 -0
  55. data/spec/acceptance/log_from_organizer_spec.rb +154 -0
  56. data/spec/acceptance/message_localization_spec.rb +118 -0
  57. data/spec/acceptance/not_having_call_method_warning_spec.rb +39 -0
  58. data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
  59. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +65 -0
  60. data/spec/acceptance/organizer/execute_spec.rb +46 -0
  61. data/spec/acceptance/organizer/iterate_spec.rb +37 -0
  62. data/spec/acceptance/organizer/reduce_if_spec.rb +51 -0
  63. data/spec/acceptance/organizer/reduce_until_spec.rb +43 -0
  64. data/spec/acceptance/organizer/with_callback_spec.rb +110 -0
  65. data/spec/acceptance/rollback_spec.rb +132 -0
  66. data/spec/acceptance/skip_all_warning_spec.rb +20 -0
  67. data/spec/acceptance/testing/context_factory_spec.rb +54 -0
  68. data/spec/action_expected_keys_spec.rb +63 -0
  69. data/spec/action_expects_and_promises_spec.rb +93 -0
  70. data/spec/action_promised_keys_spec.rb +122 -0
  71. data/spec/action_spec.rb +89 -0
  72. data/spec/context/inspect_spec.rb +57 -0
  73. data/spec/context_spec.rb +197 -0
  74. data/spec/examples/amount_spec.rb +77 -0
  75. data/spec/examples/controller_spec.rb +63 -0
  76. data/spec/examples/validate_address_spec.rb +37 -0
  77. data/spec/lib/deterministic/class_mixin_spec.rb +24 -0
  78. data/spec/lib/deterministic/currify_spec.rb +88 -0
  79. data/spec/lib/deterministic/monad_axioms.rb +44 -0
  80. data/spec/lib/deterministic/monad_spec.rb +45 -0
  81. data/spec/lib/deterministic/null_spec.rb +58 -0
  82. data/spec/lib/deterministic/option_spec.rb +133 -0
  83. data/spec/lib/deterministic/result/failure_spec.rb +65 -0
  84. data/spec/lib/deterministic/result/result_map_spec.rb +154 -0
  85. data/spec/lib/deterministic/result/result_shared.rb +24 -0
  86. data/spec/lib/deterministic/result/success_spec.rb +41 -0
  87. data/spec/lib/deterministic/result_spec.rb +63 -0
  88. data/spec/lib/enum_spec.rb +112 -0
  89. data/spec/localization_adapter_spec.rb +83 -0
  90. data/spec/organizer/with_reducer_spec.rb +56 -0
  91. data/spec/organizer_key_aliases_spec.rb +29 -0
  92. data/spec/organizer_spec.rb +93 -0
  93. data/spec/readme_spec.rb +47 -0
  94. data/spec/sample/calculates_order_tax_action_spec.rb +16 -0
  95. data/spec/sample/calculates_tax_spec.rb +30 -0
  96. data/spec/sample/looks_up_tax_percentage_action_spec.rb +53 -0
  97. data/spec/sample/provides_free_shipping_action_spec.rb +25 -0
  98. data/spec/sample/tax/calculates_order_tax_action.rb +9 -0
  99. data/spec/sample/tax/calculates_tax.rb +11 -0
  100. data/spec/sample/tax/looks_up_tax_percentage_action.rb +27 -0
  101. data/spec/sample/tax/provides_free_shipping_action.rb +10 -0
  102. data/spec/spec_helper.rb +24 -0
  103. data/spec/support.rb +1 -0
  104. data/spec/test_doubles.rb +552 -0
  105. data/spec/testing/context_factory/iterate_spec.rb +39 -0
  106. data/spec/testing/context_factory/reduce_if_spec.rb +40 -0
  107. data/spec/testing/context_factory/reduce_until_spec.rb +40 -0
  108. data/spec/testing/context_factory/with_callback_spec.rb +38 -0
  109. data/spec/testing/context_factory_spec.rb +55 -0
  110. metadata +285 -0
@@ -0,0 +1,254 @@
1
+ module FunctionalLightService
2
+ module Enum
3
+ class MatchError < StandardError; end
4
+ end
5
+
6
+ class EnumBuilder
7
+ def initialize(parent)
8
+ @parent = parent
9
+ end
10
+
11
+ class DataType
12
+ module AnyEnum
13
+ include FunctionalLightService::Monad
14
+
15
+ def match(&block)
16
+ parent.match(self, &block)
17
+ end
18
+
19
+ def to_s
20
+ value.to_s
21
+ end
22
+
23
+ def name
24
+ self.class.name.split("::")[-1]
25
+ end
26
+
27
+ # Returns array. Will fail on Nullary objects.
28
+ # TODO: define a Unary module so we can define this method differently on Unary vs Binary
29
+ def wrapped_values
30
+ if is_a?(FunctionalLightService::EnumBuilder::DataType::Binary)
31
+ value.values
32
+ else
33
+ [value]
34
+ end
35
+ end
36
+ end
37
+
38
+ module Nullary
39
+ def initialize(*_args)
40
+ @value = nil
41
+ end
42
+
43
+ def inspect
44
+ name
45
+ end
46
+ end
47
+
48
+ # TODO: this should probably be named Multary
49
+ module Binary
50
+ def initialize(*init)
51
+ unless (init.count == 1 && init[0].is_a?(Hash)) || init.count == args.count
52
+ raise ArgumentError, "Expected arguments for #{args}, got #{init}"
53
+ end
54
+
55
+ @value = if init.count == 1 && init[0].is_a?(Hash)
56
+ Hash[args.zip(init[0].values)]
57
+ else
58
+ Hash[args.zip(init)]
59
+ end
60
+ end
61
+
62
+ def inspect
63
+ params = value.map { |k, v| "#{k}: #{v.inspect}" }
64
+ "#{name}(#{params.join(', ')})"
65
+ end
66
+ end
67
+
68
+ # rubocop:disable Metrics/MethodLength
69
+ def self.create(parent, args)
70
+ # rubocop:disable Style/AccessModifierDeclarations
71
+ if args.include? :value
72
+ raise ArgumentError, "#{args} may not contain the reserved name :value"
73
+ end
74
+
75
+ dt = Class.new(parent)
76
+
77
+ dt.instance_eval do
78
+ public_class_method :new
79
+ include AnyEnum
80
+ define_method(:args) { args }
81
+
82
+ define_method(:parent) { parent }
83
+ private :parent
84
+ end
85
+
86
+ case args.count
87
+ when 0
88
+ dt.instance_eval do
89
+ include Nullary
90
+ private :value
91
+ end
92
+ when 1
93
+ dt.instance_eval do
94
+ define_method(args[0].to_sym) { value }
95
+ end
96
+ else
97
+ dt.instance_eval do
98
+ include Binary
99
+
100
+ args.each do |m|
101
+ define_method(m) do
102
+ @value[m]
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ dt
109
+ # rubocop:enable Style/AccessModifierDeclarations
110
+ end
111
+ # rubocop:enable Metrics/MethodLength
112
+
113
+ class << self
114
+ # rubocop:disable Style/AccessModifierDeclarations
115
+ public :new
116
+ # rubocop:enable Style/AccessModifierDeclarations
117
+ end
118
+ end
119
+
120
+ def method_missing(m, *args)
121
+ if @parent.const_defined?(m)
122
+ super
123
+ else
124
+ @parent.const_set(m, DataType.create(@parent, args))
125
+ end
126
+ end
127
+ end
128
+
129
+ module_function
130
+
131
+ # rubocop:disable Metrics/AbcSize
132
+ # rubocop:disable Metrics/CyclomaticComplexity
133
+ # rubocop:disable Metrics/MethodLength
134
+ # rubocop:disable Metrics/PerceivedComplexity
135
+ def enum(&block)
136
+ mod = Class.new do # the enum to be built
137
+ private_class_method :new
138
+
139
+ def self.match(obj, &block)
140
+ caller_ctx = block.binding.eval 'self'
141
+
142
+ matcher = self::Matcher.new(obj)
143
+ matcher.instance_eval(&block)
144
+
145
+ variants_in_match = matcher.matches.collect do |e|
146
+ e[1].name.split('::')[-1].to_sym
147
+ end.uniq.sort
148
+ variants_not_covered = variants - variants_in_match
149
+ unless variants_not_covered.empty?
150
+ raise Enum::MatchError, "Match is non-exhaustive, #{variants_not_covered} not covered"
151
+ end
152
+
153
+ type_matches = matcher.matches.select { |r| r[0].is_a?(r[1]) }
154
+
155
+ type_matches.each do |match|
156
+ obj, _type, block, args, guard = match
157
+
158
+ return caller_ctx.instance_eval(&block) if args.count.zero?
159
+
160
+ if args.count != obj.args.count
161
+ msg = "Pattern (#{args.join(', ')}) must match (#{obj.args.join(', ')})"
162
+ raise Enum::MatchError, msg
163
+ end
164
+
165
+ guard_ctx = guard_context(obj, args)
166
+ return caller_ctx.instance_exec(* obj.wrapped_values, &block) unless guard
167
+
168
+ if guard && guard_ctx.instance_exec(obj, &guard)
169
+ return caller_ctx.instance_exec(* obj.wrapped_values, &block)
170
+ end
171
+ end
172
+
173
+ raise Enum::MatchError, "No match could be made"
174
+ end
175
+
176
+ def self.variants
177
+ constants - %i[Matcher MatchError]
178
+ end
179
+
180
+ def self.guard_context(obj, args)
181
+ if obj.is_a?(FunctionalLightService::EnumBuilder::DataType::Binary)
182
+ Struct.new(*args).new(*obj.value.values)
183
+ else
184
+ Struct.new(*args).new(obj.value)
185
+ end
186
+ end
187
+ end
188
+ enum = EnumBuilder.new(mod)
189
+ enum.instance_eval(&block)
190
+
191
+ type_variants = mod.constants
192
+
193
+ matcher = Class.new do
194
+ def initialize(obj)
195
+ @obj = obj
196
+ @matches = []
197
+ @vars = []
198
+ end
199
+
200
+ attr_reader :matches, :vars
201
+
202
+ def where(&guard)
203
+ guard
204
+ end
205
+
206
+ type_variants.each do |m|
207
+ define_method(m) do |guard = nil, &inner_block|
208
+ raise ArgumentError, "No block given to `#{m}`" if inner_block.nil?
209
+
210
+ params_spec = inner_block.parameters
211
+ if params_spec.any? { |spec| spec.size < 2 }
212
+ msg = "Unnamed param found in block parameters: #{params_spec.inspect}"
213
+ raise ArgumentError, msg
214
+ end
215
+ if params_spec.any? { |spec| spec[0] != :req && spec[0] != :opt }
216
+ msg = "Only :req & :opt params allowed; parameters=#{params_spec.inspect}"
217
+ raise ArgumentError, msg
218
+ end
219
+
220
+ args = params_spec.map { |spec| spec[1] }
221
+
222
+ type = Kernel.eval("#{mod.name}::#{m}")
223
+
224
+ guard = nil if guard && !guard.is_a?(Proc)
225
+
226
+ @matches << [@obj, type, inner_block, args, guard]
227
+ end
228
+ end
229
+ end
230
+
231
+ mod.const_set(:Matcher, matcher)
232
+
233
+ type_variants.each do |variant|
234
+ mod.singleton_class.class_exec do
235
+ define_method(variant) do |*args|
236
+ const_get(variant).new(*args)
237
+ end
238
+ end
239
+ end
240
+ mod
241
+ end
242
+ # rubocop:enable Metrics/AbcSize
243
+ # rubocop:enable Metrics/CyclomaticComplexity
244
+ # rubocop:enable Metrics/MethodLength
245
+ # rubocop:enable Metrics/PerceivedComplexity
246
+
247
+ def impl(enum_type, &block)
248
+ enum_type.variants.each do |v|
249
+ name = "#{enum_type.name}::#{v}"
250
+ type = Kernel.eval(name)
251
+ type.class_eval(&block)
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,14 @@
1
+ class Object
2
+ def null?
3
+ false
4
+ end
5
+
6
+ def some?
7
+ true
8
+ end
9
+ end
10
+ # rubocop:disable Naming/MethodName
11
+ def Maybe(obj)
12
+ obj.nil? ? Null.instance : obj
13
+ end
14
+ # rubocop:enable Naming/MethodName
@@ -0,0 +1,66 @@
1
+ module FunctionalLightService
2
+ module Monad
3
+ class NotMonadError < StandardError; end
4
+
5
+ # Basicly the `pure` function
6
+ def initialize(init)
7
+ @value = join(init)
8
+ end
9
+
10
+ # If the passed value is monad already, get the value to avoid nesting
11
+ # M[M[A]] is equivalent to M[A]
12
+ def join(other)
13
+ if other.is_a? self.class
14
+ other.value
15
+ else
16
+ other
17
+ end
18
+ end
19
+
20
+ # The functor: takes a function (a -> b) and applies it to the inner value of the monad (Ma),
21
+ # boxes it back to the same monad (Mb)
22
+ # fmap :: (a -> b) -> M a -> M b
23
+ def fmap(proc = nil, &block)
24
+ result = (proc || block).call(value)
25
+ self.class.new(result)
26
+ end
27
+
28
+ # The monad: takes a function which returns a monad (of the same type), applies the function
29
+ # bind :: (a -> Mb) -> M a -> M b
30
+ # the self.class, i.e. the containing monad is passed as a second (optional) arg to the function
31
+ def bind(proc = nil, &block)
32
+ (proc || block).call(value).tap do |result|
33
+ # rubocop:disable Style/CaseEquality
34
+ parent = self.class.superclass === Object ? self.class : self.class.superclass
35
+ # rubocop:enable Style/CaseEquality
36
+ unless result.is_a? parent
37
+ raise NotMonadError, "Expected #{result.inspect} to be an #{parent}"
38
+ end
39
+ end
40
+ end
41
+ alias :'>>=' :bind
42
+
43
+ # Get the underlying value, return in Haskell
44
+ # return :: M a -> a
45
+ def value
46
+ @value
47
+ end
48
+
49
+ def to_s
50
+ value.to_s
51
+ end
52
+
53
+ # Two monads are equivalent if they are of the same type and when their values are equal
54
+ def ==(other)
55
+ return false unless other.is_a? self.class
56
+
57
+ @value == other.instance_variable_get(:@value)
58
+ end
59
+
60
+ # Return the string representation of the Monad
61
+ def inspect
62
+ pretty_class_name = self.class.name.split('::')[-1]
63
+ "#{pretty_class_name}(#{value.inspect})"
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,74 @@
1
+ # The simplest NullObject there can be
2
+ class Null
3
+ class << self
4
+ def method_missing(m, *args)
5
+ if m == :new
6
+ super
7
+ else
8
+ Null.instance
9
+ end
10
+ end
11
+
12
+ def instance
13
+ @instance ||= new([])
14
+ end
15
+
16
+ def null?
17
+ true
18
+ end
19
+
20
+ def some?
21
+ false
22
+ end
23
+
24
+ def mimic(klas)
25
+ new(klas.instance_methods(false))
26
+ end
27
+
28
+ def ==(other)
29
+ other.respond_to?(:null?) && other.null?
30
+ end
31
+ end
32
+ private_class_method :new
33
+
34
+ def initialize(methods)
35
+ @methods = methods
36
+ end
37
+
38
+ # implicit conversions
39
+ def to_str
40
+ ''
41
+ end
42
+
43
+ def to_ary
44
+ []
45
+ end
46
+
47
+ def method_missing(m, *args)
48
+ return self if respond_to?(m)
49
+
50
+ super
51
+ end
52
+
53
+ def null?
54
+ true
55
+ end
56
+
57
+ def some?
58
+ false
59
+ end
60
+
61
+ def respond_to?(m, include_private = false)
62
+ return true if @methods.empty? || @methods.include?(m)
63
+
64
+ super
65
+ end
66
+
67
+ def inspect
68
+ 'Null'
69
+ end
70
+
71
+ def ==(other)
72
+ other.respond_to?(:null?) && other.null?
73
+ end
74
+ end
@@ -0,0 +1,99 @@
1
+ module FunctionalLightService
2
+ Option = FunctionalLightService.enum do
3
+ Some(:s)
4
+ None()
5
+ end
6
+
7
+ class Option
8
+ class << self
9
+ def some?(expr)
10
+ to_option(expr) { expr.nil? }
11
+ end
12
+
13
+ def any?(expr)
14
+ to_option(expr) { expr.nil? || (expr.respond_to?(:empty?) && expr.empty?) }
15
+ end
16
+
17
+ def to_option(expr)
18
+ yield(expr) ? None.new : Some.new(expr)
19
+ end
20
+
21
+ def try!
22
+ yield
23
+ rescue StandardError
24
+ None.new
25
+ end
26
+ end
27
+ end
28
+
29
+ # rubocop:disable Metrics/BlockLength
30
+ impl(Option) do
31
+ class NoneValueError < StandardError; end
32
+
33
+ def fmap
34
+ match do
35
+ Some() { |s| self.class.new(yield(s)) }
36
+ None() { self }
37
+ end
38
+ end
39
+
40
+ def map(&fn)
41
+ match do
42
+ Some() { |_s| bind(&fn) }
43
+ None() { self }
44
+ end
45
+ end
46
+
47
+ def some?
48
+ is_a? Option::Some
49
+ end
50
+
51
+ def none?
52
+ is_a? Option::None
53
+ end
54
+
55
+ alias :empty? :none?
56
+
57
+ def value_or(n)
58
+ match do
59
+ Some() { |s| s }
60
+ None() { n }
61
+ end
62
+ end
63
+
64
+ def value_to_a
65
+ @value
66
+ end
67
+
68
+ def +(other)
69
+ match do
70
+ None() { other }
71
+ Some(where { !other.is_a?(Option) }) { |_| raise TypeError, "Other must be an #{Option}" }
72
+ Some(where { other.some? }) { |s| Option::Some.new(s + other.value) }
73
+ Some() { |_| self }
74
+ end
75
+ end
76
+ end
77
+ # rubocop:enable Metrics/BlockLength
78
+
79
+ module Prelude
80
+ module Option
81
+ None = FunctionalLightService::Option::None.new
82
+ Option = FunctionalLightService::Option
83
+ # rubocop:disable Naming/MethodName
84
+ def Some(s)
85
+ FunctionalLightService::Option::Some.new(s)
86
+ end
87
+
88
+ def None
89
+ FunctionalLightService::Prelude::Option::None
90
+ end
91
+
92
+ def Option
93
+ FunctionalLightService::Option
94
+ end
95
+ # rubocop:enable Naming/MethodName
96
+ # include Option
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,122 @@
1
+ module FunctionalLightService
2
+ Result = FunctionalLightService.enum do
3
+ Success(:s)
4
+ Failure(:f)
5
+ end
6
+
7
+ class Result
8
+ class << self
9
+ def try!
10
+ Success.new(yield)
11
+ rescue StandardError => err
12
+ Failure.new(err)
13
+ end
14
+ end
15
+ end
16
+
17
+ # rubocop:disable Metrics/BlockLength
18
+ FunctionalLightService.impl(Result) do
19
+ def map(proc = nil, &block)
20
+ match do
21
+ Success() { |_| bind(proc || block) }
22
+ Failure() { |_| self }
23
+ end
24
+ end
25
+
26
+ alias :>> :map
27
+ alias :and_then :map
28
+
29
+ def map_err(proc = nil, &block)
30
+ match do
31
+ Success() { |_| self }
32
+ Failure() { |_| bind(proc || block) }
33
+ end
34
+ end
35
+
36
+ alias :or_else :map_err
37
+
38
+ def pipe(proc = nil, &block)
39
+ (proc || block).call(self)
40
+ self
41
+ end
42
+
43
+ alias :<< :pipe
44
+
45
+ def success?
46
+ is_a? Result::Success
47
+ end
48
+
49
+ def failure?
50
+ is_a? Result::Failure
51
+ end
52
+
53
+ def or(other)
54
+ unless other.is_a? Result
55
+ msg = "Expected #{other.inspect} to be a Result"
56
+ raise FunctionalLightService::Monad::NotMonadError, msg
57
+ end
58
+
59
+ match do
60
+ Success() { |_| self }
61
+ Failure() { |_| other }
62
+ end
63
+ end
64
+
65
+ def and(other)
66
+ unless other.is_a? Result
67
+ msg = "Expected #{other.inspect} to be a Result"
68
+ raise FunctionalLightService::Monad::NotMonadError, msg
69
+ end
70
+
71
+ match do
72
+ Success() { |_| other }
73
+ Failure() { |_| self }
74
+ end
75
+ end
76
+
77
+ def +(other)
78
+ unless other.is_a? Result
79
+ msg = "Expected #{other.inspect} to be a Result"
80
+ raise FunctionalLightService::Monad::NotMonadError, msg
81
+ end
82
+
83
+ match do
84
+ Success(where { other.success? }) { |s| Result::Success.new(s + other.value) }
85
+ Failure(where { other.failure? }) { |f| Result::Failure.new(f + other.value) }
86
+ Success() { |_| other } # implied other.failure?
87
+ Failure() { |_| self } # implied other.success?
88
+ end
89
+ end
90
+
91
+ def try(proc = nil, &block)
92
+ map(proc, &block)
93
+ rescue StandardError => e
94
+ Result::Failure.new(e)
95
+ end
96
+
97
+ alias :>= :try
98
+ end
99
+ # rubocop:enable Metrics/BlockLength
100
+ end
101
+
102
+ module FunctionalLightService
103
+ module Prelude
104
+ module Result
105
+ # rubocop:disable Naming/MethodName
106
+ def try!(&block)
107
+ FunctionalLightService::Result.try!(&block)
108
+ end
109
+
110
+ def Success(s)
111
+ FunctionalLightService::Result::Success.new(s)
112
+ end
113
+
114
+ def Failure(f)
115
+ FunctionalLightService::Result::Failure.new(f)
116
+ end
117
+ # rubocop:enable Naming/MethodName
118
+ end
119
+
120
+ include Result
121
+ end
122
+ end
@@ -0,0 +1,44 @@
1
+ module FunctionalLightService
2
+ class LocalizationAdapter
3
+ def failure(message_or_key, action_class, i18n_options = {})
4
+ find_translated_message(message_or_key,
5
+ action_class,
6
+ i18n_options,
7
+ :type => :failure)
8
+ end
9
+
10
+ def success(message_or_key, action_class, i18n_options = {})
11
+ find_translated_message(message_or_key,
12
+ action_class,
13
+ i18n_options,
14
+ :type => :success)
15
+ end
16
+
17
+ private
18
+
19
+ def find_translated_message(message_or_key,
20
+ action_class,
21
+ i18n_options,
22
+ type)
23
+ if message_or_key.is_a?(Symbol)
24
+ i18n_options.merge!(type)
25
+ translate(message_or_key, action_class, i18n_options)
26
+ else
27
+ message_or_key
28
+ end
29
+ end
30
+
31
+ def translate(key, action_class, options = {})
32
+ type = options.delete(:type)
33
+
34
+ scope = i18n_scope_from_class(action_class, type)
35
+ options[:scope] = scope
36
+
37
+ I18n.t(key, options)
38
+ end
39
+
40
+ def i18n_scope_from_class(action_class, type)
41
+ "#{action_class.name.underscore}.light_service.#{type.to_s.pluralize}"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,14 @@
1
+ module FunctionalLightService
2
+ module Organizer
3
+ class Execute
4
+ def self.run(code_block)
5
+ ->(ctx) do
6
+ return ctx if ctx.stop_processing?
7
+
8
+ code_block.call(ctx)
9
+ ctx
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ module FunctionalLightService
2
+ module Organizer
3
+ class Iterate
4
+ extend ScopedReducable
5
+
6
+ def self.run(organizer, collection_key, steps)
7
+ ->(ctx) do
8
+ return ctx if ctx.stop_processing?
9
+
10
+ collection = ctx[collection_key]
11
+ item_key = collection_key.to_s.singularize.to_sym
12
+ collection.each do |item|
13
+ ctx[item_key] = item
14
+ ctx = scoped_reduce(organizer, ctx, steps)
15
+ end
16
+
17
+ ctx
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end