functional-light-service 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +3 -0
- data/.rubocop.yml +72 -0
- data/.travis.yml +22 -0
- data/Appraisals +3 -0
- data/CHANGELOG.md +37 -0
- data/CODE_OF_CONDUCT.md +22 -0
- data/Gemfile +6 -0
- data/LICENSE +22 -0
- data/README.md +1424 -0
- data/Rakefile +12 -0
- data/VERSION +1 -0
- data/functional-light-service.gemspec +26 -0
- data/gemfiles/activesupport_5.gemfile +8 -0
- data/gemfiles/activesupport_5.gemfile.lock +82 -0
- data/lib/functional-light-service/action.rb +102 -0
- data/lib/functional-light-service/configuration.rb +24 -0
- data/lib/functional-light-service/context/key_verifier.rb +118 -0
- data/lib/functional-light-service/context.rb +165 -0
- data/lib/functional-light-service/errors.rb +6 -0
- data/lib/functional-light-service/functional/enum.rb +254 -0
- data/lib/functional-light-service/functional/maybe.rb +14 -0
- data/lib/functional-light-service/functional/monad.rb +66 -0
- data/lib/functional-light-service/functional/null.rb +74 -0
- data/lib/functional-light-service/functional/option.rb +99 -0
- data/lib/functional-light-service/functional/result.rb +122 -0
- data/lib/functional-light-service/localization_adapter.rb +44 -0
- data/lib/functional-light-service/organizer/execute.rb +14 -0
- data/lib/functional-light-service/organizer/iterate.rb +22 -0
- data/lib/functional-light-service/organizer/reduce_if.rb +17 -0
- data/lib/functional-light-service/organizer/reduce_until.rb +20 -0
- data/lib/functional-light-service/organizer/scoped_reducable.rb +13 -0
- data/lib/functional-light-service/organizer/verify_call_method_exists.rb +28 -0
- data/lib/functional-light-service/organizer/with_callback.rb +26 -0
- data/lib/functional-light-service/organizer/with_reducer.rb +71 -0
- data/lib/functional-light-service/organizer/with_reducer_factory.rb +18 -0
- data/lib/functional-light-service/organizer/with_reducer_log_decorator.rb +105 -0
- data/lib/functional-light-service/organizer.rb +105 -0
- data/lib/functional-light-service/testing/context_factory.rb +40 -0
- data/lib/functional-light-service/testing.rb +1 -0
- data/lib/functional-light-service/version.rb +3 -0
- data/lib/functional-light-service.rb +29 -0
- data/resources/fail_actions.png +0 -0
- data/resources/light-service.png +0 -0
- data/resources/organizer_and_actions.png +0 -0
- data/resources/skip_actions.png +0 -0
- data/spec/acceptance/add_numbers_spec.rb +11 -0
- data/spec/acceptance/after_actions_spec.rb +71 -0
- data/spec/acceptance/around_each_spec.rb +19 -0
- data/spec/acceptance/before_actions_spec.rb +98 -0
- data/spec/acceptance/custom_log_from_organizer_spec.rb +60 -0
- data/spec/acceptance/fail_spec.rb +24 -0
- data/spec/acceptance/include_warning_spec.rb +29 -0
- data/spec/acceptance/log_from_organizer_spec.rb +154 -0
- data/spec/acceptance/message_localization_spec.rb +118 -0
- data/spec/acceptance/not_having_call_method_warning_spec.rb +39 -0
- data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
- data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +65 -0
- data/spec/acceptance/organizer/execute_spec.rb +46 -0
- data/spec/acceptance/organizer/iterate_spec.rb +37 -0
- data/spec/acceptance/organizer/reduce_if_spec.rb +51 -0
- data/spec/acceptance/organizer/reduce_until_spec.rb +43 -0
- data/spec/acceptance/organizer/with_callback_spec.rb +110 -0
- data/spec/acceptance/rollback_spec.rb +132 -0
- data/spec/acceptance/skip_all_warning_spec.rb +20 -0
- data/spec/acceptance/testing/context_factory_spec.rb +54 -0
- data/spec/action_expected_keys_spec.rb +63 -0
- data/spec/action_expects_and_promises_spec.rb +93 -0
- data/spec/action_promised_keys_spec.rb +122 -0
- data/spec/action_spec.rb +89 -0
- data/spec/context/inspect_spec.rb +57 -0
- data/spec/context_spec.rb +197 -0
- data/spec/examples/amount_spec.rb +77 -0
- data/spec/examples/controller_spec.rb +63 -0
- data/spec/examples/validate_address_spec.rb +37 -0
- data/spec/lib/deterministic/class_mixin_spec.rb +24 -0
- data/spec/lib/deterministic/currify_spec.rb +88 -0
- data/spec/lib/deterministic/monad_axioms.rb +44 -0
- data/spec/lib/deterministic/monad_spec.rb +45 -0
- data/spec/lib/deterministic/null_spec.rb +58 -0
- data/spec/lib/deterministic/option_spec.rb +133 -0
- data/spec/lib/deterministic/result/failure_spec.rb +65 -0
- data/spec/lib/deterministic/result/result_map_spec.rb +154 -0
- data/spec/lib/deterministic/result/result_shared.rb +24 -0
- data/spec/lib/deterministic/result/success_spec.rb +41 -0
- data/spec/lib/deterministic/result_spec.rb +63 -0
- data/spec/lib/enum_spec.rb +112 -0
- data/spec/localization_adapter_spec.rb +83 -0
- data/spec/organizer/with_reducer_spec.rb +56 -0
- data/spec/organizer_key_aliases_spec.rb +29 -0
- data/spec/organizer_spec.rb +93 -0
- data/spec/readme_spec.rb +47 -0
- data/spec/sample/calculates_order_tax_action_spec.rb +16 -0
- data/spec/sample/calculates_tax_spec.rb +30 -0
- data/spec/sample/looks_up_tax_percentage_action_spec.rb +53 -0
- data/spec/sample/provides_free_shipping_action_spec.rb +25 -0
- data/spec/sample/tax/calculates_order_tax_action.rb +9 -0
- data/spec/sample/tax/calculates_tax.rb +11 -0
- data/spec/sample/tax/looks_up_tax_percentage_action.rb +27 -0
- data/spec/sample/tax/provides_free_shipping_action.rb +10 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support.rb +1 -0
- data/spec/test_doubles.rb +552 -0
- data/spec/testing/context_factory/iterate_spec.rb +39 -0
- data/spec/testing/context_factory/reduce_if_spec.rb +40 -0
- data/spec/testing/context_factory/reduce_until_spec.rb +40 -0
- data/spec/testing/context_factory/with_callback_spec.rb +38 -0
- data/spec/testing/context_factory_spec.rb +55 -0
- 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,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,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
|