functional-light-service 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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