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.
- 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,17 @@
|
|
|
1
|
+
module FunctionalLightService
|
|
2
|
+
module Organizer
|
|
3
|
+
class ReduceIf
|
|
4
|
+
extend ScopedReducable
|
|
5
|
+
|
|
6
|
+
def self.run(organizer, condition_block, steps)
|
|
7
|
+
->(ctx) do
|
|
8
|
+
return ctx if ctx.stop_processing?
|
|
9
|
+
|
|
10
|
+
ctx = scoped_reduce(organizer, ctx, steps) if condition_block.call(ctx)
|
|
11
|
+
|
|
12
|
+
ctx
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module FunctionalLightService
|
|
2
|
+
module Organizer
|
|
3
|
+
class ReduceUntil
|
|
4
|
+
extend ScopedReducable
|
|
5
|
+
|
|
6
|
+
def self.run(organizer, condition_block, steps)
|
|
7
|
+
->(ctx) do
|
|
8
|
+
return ctx if ctx.stop_processing?
|
|
9
|
+
|
|
10
|
+
loop do
|
|
11
|
+
ctx = scoped_reduce(organizer, ctx, steps)
|
|
12
|
+
break if condition_block.call(ctx) || ctx.failure?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
ctx
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module FunctionalLightService
|
|
2
|
+
module Organizer
|
|
3
|
+
module ScopedReducable
|
|
4
|
+
def scoped_reduce(organizer, ctx, steps)
|
|
5
|
+
ctx.reset_skip_remaining! unless ctx.failure?
|
|
6
|
+
ctx = organizer.with(ctx).reduce([steps])
|
|
7
|
+
ctx.reset_skip_remaining! unless ctx.failure?
|
|
8
|
+
|
|
9
|
+
ctx
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module FunctionalLightService
|
|
2
|
+
module Organizer
|
|
3
|
+
# We need to make sure existing users will
|
|
4
|
+
# use `call` method name going forward.
|
|
5
|
+
# This should be removed eventually.
|
|
6
|
+
class VerifyCallMethodExists
|
|
7
|
+
def self.run(klass, first_caller = '')
|
|
8
|
+
invoker_method = caller_method(first_caller)
|
|
9
|
+
return if invoker_method == 'call'
|
|
10
|
+
|
|
11
|
+
call_method_exists = klass.methods.include?(:call)
|
|
12
|
+
return if call_method_exists
|
|
13
|
+
|
|
14
|
+
warning_msg = "The <#{klass.name}> class is an organizer, " \
|
|
15
|
+
"its entry method (the one that calls with & reduce) " \
|
|
16
|
+
"should be named `call`. " \
|
|
17
|
+
"Please use #{klass}.call going forward."
|
|
18
|
+
ActiveSupport::Deprecation.warn(warning_msg)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.caller_method(first_caller)
|
|
22
|
+
return nil unless first_caller =~ /`(.*)'/
|
|
23
|
+
|
|
24
|
+
Regexp.last_match[1]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module FunctionalLightService
|
|
2
|
+
module Organizer
|
|
3
|
+
class WithCallback
|
|
4
|
+
extend ScopedReducable
|
|
5
|
+
|
|
6
|
+
def self.run(organizer, action, steps)
|
|
7
|
+
->(ctx) do
|
|
8
|
+
return ctx if ctx.stop_processing?
|
|
9
|
+
|
|
10
|
+
# This will only allow 2 level deep nesting of callbacks
|
|
11
|
+
previous_callback = ctx[:callback]
|
|
12
|
+
|
|
13
|
+
ctx[:callback] = ->(context) do
|
|
14
|
+
ctx = scoped_reduce(organizer, context, steps)
|
|
15
|
+
ctx
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
ctx = action.execute(ctx)
|
|
19
|
+
ctx[:callback] = previous_callback
|
|
20
|
+
|
|
21
|
+
ctx
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module FunctionalLightService
|
|
2
|
+
module Organizer
|
|
3
|
+
class WithReducer
|
|
4
|
+
attr_reader :context
|
|
5
|
+
|
|
6
|
+
def with(data = {})
|
|
7
|
+
@context = FunctionalLightService::Context.make(data)
|
|
8
|
+
self
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def around_each(handler)
|
|
12
|
+
@around_each_handler = handler
|
|
13
|
+
self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def around_each_handler
|
|
17
|
+
@around_each_handler ||= Class.new do
|
|
18
|
+
def self.call(_context)
|
|
19
|
+
yield
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def reduce(*actions)
|
|
25
|
+
raise "No action(s) were provided" if actions.empty?
|
|
26
|
+
|
|
27
|
+
actions.flatten!
|
|
28
|
+
|
|
29
|
+
actions.each_with_object(context) do |action, current_context|
|
|
30
|
+
invoke_action(current_context, action)
|
|
31
|
+
rescue FailWithRollbackError
|
|
32
|
+
reduce_rollback(actions)
|
|
33
|
+
ensure
|
|
34
|
+
# For logging
|
|
35
|
+
yield(current_context, action) if block_given?
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def reduce_rollback(actions)
|
|
40
|
+
reversable_actions(actions)
|
|
41
|
+
.reverse
|
|
42
|
+
.reduce(context) do |context, action|
|
|
43
|
+
if action.respond_to?(:rollback)
|
|
44
|
+
action.rollback(context)
|
|
45
|
+
else
|
|
46
|
+
context
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def invoke_action(current_context, action)
|
|
54
|
+
around_each_handler.call(current_context) do
|
|
55
|
+
if action.respond_to?(:call)
|
|
56
|
+
action.call(current_context)
|
|
57
|
+
else
|
|
58
|
+
action.execute(current_context)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def reversable_actions(actions)
|
|
64
|
+
index_of_current_action = actions.index(@context.current_action) || 0
|
|
65
|
+
|
|
66
|
+
# Reverse from the point where the fail was triggered
|
|
67
|
+
actions.take(index_of_current_action + 1)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module FunctionalLightService
|
|
2
|
+
module Organizer
|
|
3
|
+
class WithReducerFactory
|
|
4
|
+
def self.make(monitored_organizer)
|
|
5
|
+
logger = monitored_organizer.logger || FunctionalLightService::Configuration.logger
|
|
6
|
+
decorated = WithReducer.new
|
|
7
|
+
|
|
8
|
+
return decorated if logger.nil?
|
|
9
|
+
|
|
10
|
+
WithReducerLogDecorator.new(
|
|
11
|
+
monitored_organizer,
|
|
12
|
+
:decorated => decorated,
|
|
13
|
+
:logger => logger
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
module FunctionalLightService
|
|
2
|
+
module Organizer
|
|
3
|
+
class WithReducerLogDecorator
|
|
4
|
+
attr_reader :logged, :logger, :decorated, :organizer
|
|
5
|
+
|
|
6
|
+
alias logged? logged
|
|
7
|
+
|
|
8
|
+
def initialize(organizer, decorated: WithReducer.new, logger:)
|
|
9
|
+
@decorated = decorated
|
|
10
|
+
@organizer = organizer
|
|
11
|
+
@logger = logger
|
|
12
|
+
@logged = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def with(data = {})
|
|
16
|
+
logger.info { "[FunctionalLightService] - calling organizer <#{organizer}>" }
|
|
17
|
+
|
|
18
|
+
decorated.with(data)
|
|
19
|
+
|
|
20
|
+
logger.info do
|
|
21
|
+
"[FunctionalLightService] - keys in context: " \
|
|
22
|
+
"#{extract_keys(decorated.context.keys)}"
|
|
23
|
+
end
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def around_each(handler)
|
|
28
|
+
decorated.around_each(handler)
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def reduce(*actions)
|
|
33
|
+
decorated.reduce(*actions) do |context, action|
|
|
34
|
+
next context if logged?
|
|
35
|
+
|
|
36
|
+
if has_failure?(context)
|
|
37
|
+
write_failure_log(context, action)
|
|
38
|
+
next context
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if skip_remaining?(context)
|
|
42
|
+
write_skip_remaining_log(context, action)
|
|
43
|
+
next context
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
write_log(action, context)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def write_log(action, context)
|
|
53
|
+
return unless logger.info?
|
|
54
|
+
|
|
55
|
+
logger.info("[FunctionalLightService] - executing <#{action}>")
|
|
56
|
+
log_expects(action)
|
|
57
|
+
log_promises(action)
|
|
58
|
+
logger.info("[FunctionalLightService] - keys in context: "\
|
|
59
|
+
"#{extract_keys(context.keys)}")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def log_expects(action)
|
|
63
|
+
return unless defined?(action.expects) && action.expects.any?
|
|
64
|
+
|
|
65
|
+
logger.info("[FunctionalLightService] - expects: " \
|
|
66
|
+
"#{extract_keys(action.expects)}")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def log_promises(action)
|
|
70
|
+
return unless defined?(action.promises) && action.promises.any?
|
|
71
|
+
|
|
72
|
+
logger.info("[FunctionalLightService] - promises: " \
|
|
73
|
+
"#{extract_keys(action.promises)}")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def extract_keys(keys)
|
|
77
|
+
keys.map { |key| ":#{key}" }.join(', ')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def has_failure?(context)
|
|
81
|
+
context.respond_to?(:failure?) && context.failure?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def write_failure_log(context, action)
|
|
85
|
+
logger.warn("[FunctionalLightService] - :-((( <#{action}> has failed...")
|
|
86
|
+
logger.warn("[FunctionalLightService] - context message: #{context.message}")
|
|
87
|
+
@logged = true
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def skip_remaining?(context)
|
|
91
|
+
context.respond_to?(:skip_remaining?) && context.skip_remaining?
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def write_skip_remaining_log(context, action)
|
|
95
|
+
return unless logger.info?
|
|
96
|
+
|
|
97
|
+
msg = "[FunctionalLightService] - ;-) <#{action}> has decided " \
|
|
98
|
+
"to skip the rest of the actions"
|
|
99
|
+
logger.info(msg)
|
|
100
|
+
logger.info("[FunctionalLightService] - context message: #{context.message}")
|
|
101
|
+
@logged = true
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
require 'active_support/deprecation'
|
|
2
|
+
|
|
3
|
+
module FunctionalLightService
|
|
4
|
+
module Organizer
|
|
5
|
+
def self.extended(base_class)
|
|
6
|
+
base_class.extend ClassMethods
|
|
7
|
+
base_class.extend Macros
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.included(base_class)
|
|
11
|
+
warning_msg = "including FunctionalLightService::Organizer is deprecated. " \
|
|
12
|
+
"Please use `extend FunctionalLightService::Organizer` instead"
|
|
13
|
+
ActiveSupport::Deprecation.warn(warning_msg)
|
|
14
|
+
extended(base_class)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# In case this module is included
|
|
18
|
+
module ClassMethods
|
|
19
|
+
def with(data = {})
|
|
20
|
+
VerifyCallMethodExists.run(self, caller(1..1).first)
|
|
21
|
+
data[:_aliases] = @aliases if @aliases
|
|
22
|
+
|
|
23
|
+
if @before_actions
|
|
24
|
+
data[:_before_actions] = @before_actions.dup
|
|
25
|
+
@before_actions = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if @after_actions
|
|
29
|
+
data[:_after_actions] = @after_actions.dup
|
|
30
|
+
@after_actions = nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
WithReducerFactory.make(self).with(data)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def reduce(*actions)
|
|
37
|
+
with({}).reduce(actions)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def reduce_if(condition_block, steps)
|
|
41
|
+
ReduceIf.run(self, condition_block, steps)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def reduce_until(condition_block, steps)
|
|
45
|
+
ReduceUntil.run(self, condition_block, steps)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def iterate(collection_key, steps)
|
|
49
|
+
Iterate.run(self, collection_key, steps)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def execute(code_block)
|
|
53
|
+
Execute.run(code_block)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def with_callback(action, steps)
|
|
57
|
+
WithCallback.run(self, action, steps)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def log_with(logger)
|
|
61
|
+
@logger = logger
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def logger
|
|
65
|
+
@logger
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
module Macros
|
|
70
|
+
def aliases(key_hash)
|
|
71
|
+
@aliases = key_hash
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# This looks like an accessor,
|
|
75
|
+
# but it's used as a macro in the Organizer
|
|
76
|
+
def before_actions(*logic)
|
|
77
|
+
self.before_actions = logic
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def before_actions=(logic)
|
|
81
|
+
@before_actions = [logic].flatten
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def append_before_actions(action)
|
|
85
|
+
@before_actions ||= []
|
|
86
|
+
@before_actions.push(action)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# This looks like an accessor,
|
|
90
|
+
# but it's used as a macro in the Organizer
|
|
91
|
+
def after_actions(*logic)
|
|
92
|
+
self.after_actions = logic
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def after_actions=(logic)
|
|
96
|
+
@after_actions = [logic].flatten
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def append_after_actions(action)
|
|
100
|
+
@after_actions ||= []
|
|
101
|
+
@after_actions.push(action)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module FunctionalLightService
|
|
2
|
+
module Testing
|
|
3
|
+
class ContextFactory
|
|
4
|
+
attr_reader :organizer
|
|
5
|
+
|
|
6
|
+
def self.make_from(organizer)
|
|
7
|
+
new(organizer)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def for(action)
|
|
11
|
+
@organizer.append_before_actions(
|
|
12
|
+
->(ctx) do
|
|
13
|
+
if ctx.current_action == action
|
|
14
|
+
# The last `:_before_actions` hook is for
|
|
15
|
+
# ContextFactory, remove it, so it won't
|
|
16
|
+
# be invoked again
|
|
17
|
+
ctx[:_before_actions].pop
|
|
18
|
+
|
|
19
|
+
throw(:return_ctx_from_execution, ctx)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# More than one arguments can be passed to the
|
|
28
|
+
# Organizer's #call method
|
|
29
|
+
def with(*args, &block)
|
|
30
|
+
catch(:return_ctx_from_execution) do
|
|
31
|
+
@organizer.call(*args, &block)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize(organizer)
|
|
36
|
+
@organizer = organizer
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'functional-light-service/testing/context_factory'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
require 'active_support/core_ext/string'
|
|
3
|
+
|
|
4
|
+
require 'functional-light-service/version'
|
|
5
|
+
|
|
6
|
+
module FunctionalLightService; end
|
|
7
|
+
|
|
8
|
+
require 'functional-light-service/functional/monad'
|
|
9
|
+
require 'functional-light-service/functional/enum'
|
|
10
|
+
require 'functional-light-service/functional/result'
|
|
11
|
+
require 'functional-light-service/functional/option'
|
|
12
|
+
require 'functional-light-service/functional/null'
|
|
13
|
+
require 'functional-light-service/errors'
|
|
14
|
+
require 'functional-light-service/configuration'
|
|
15
|
+
require 'functional-light-service/localization_adapter'
|
|
16
|
+
require 'functional-light-service/context'
|
|
17
|
+
require 'functional-light-service/context/key_verifier'
|
|
18
|
+
require 'functional-light-service/organizer/scoped_reducable'
|
|
19
|
+
require 'functional-light-service/organizer/with_reducer'
|
|
20
|
+
require 'functional-light-service/organizer/with_reducer_log_decorator'
|
|
21
|
+
require 'functional-light-service/organizer/with_reducer_factory'
|
|
22
|
+
require 'functional-light-service/organizer/reduce_if'
|
|
23
|
+
require 'functional-light-service/organizer/reduce_until'
|
|
24
|
+
require 'functional-light-service/organizer/iterate'
|
|
25
|
+
require 'functional-light-service/organizer/execute'
|
|
26
|
+
require 'functional-light-service/organizer/with_callback'
|
|
27
|
+
require 'functional-light-service/organizer/verify_call_method_exists'
|
|
28
|
+
require 'functional-light-service/action'
|
|
29
|
+
require 'functional-light-service/organizer'
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
|
|
4
|
+
RSpec.describe TestDoubles::AdditionOrganizer do
|
|
5
|
+
it 'Adds 1, 2 and 3 to the initial value of 1' do
|
|
6
|
+
result = TestDoubles::AdditionOrganizer.call(1)
|
|
7
|
+
number = result.fetch(:number)
|
|
8
|
+
|
|
9
|
+
expect(number).to eq(7)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
|
|
4
|
+
RSpec.describe 'Action after_actions' do
|
|
5
|
+
describe 'works with simple organizers - from outside' do
|
|
6
|
+
it 'can be used to inject code block before each action' do
|
|
7
|
+
TestDoubles::AdditionOrganizer.after_actions = ->(ctx) do
|
|
8
|
+
ctx.number -= 2 if ctx.current_action == TestDoubles::AddsThreeAction
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
result = TestDoubles::AdditionOrganizer.call(0)
|
|
12
|
+
|
|
13
|
+
expect(result.fetch(:number)).to eq(4)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'works with iterator' do
|
|
17
|
+
TestDoubles::TestIterate.after_actions = [
|
|
18
|
+
->(ctx) { ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction }
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
result = TestDoubles::TestIterate.call(:number => 0,
|
|
22
|
+
:counters => [1, 2, 3, 4])
|
|
23
|
+
|
|
24
|
+
expect(result).to be_success
|
|
25
|
+
expect(result.number).to eq(-4)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe 'can be added to organizers declaratively' do
|
|
30
|
+
module AfterActions
|
|
31
|
+
class AdditionOrganizer
|
|
32
|
+
extend FunctionalLightService::Organizer
|
|
33
|
+
after_actions (->(ctx) do
|
|
34
|
+
ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction
|
|
35
|
+
end),
|
|
36
|
+
(->(ctx) do
|
|
37
|
+
ctx.number -= 3 if ctx.current_action == TestDoubles::AddsThreeAction
|
|
38
|
+
end)
|
|
39
|
+
|
|
40
|
+
def self.call(number)
|
|
41
|
+
with(:number => number).reduce(actions)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.actions
|
|
45
|
+
[
|
|
46
|
+
TestDoubles::AddsOneAction,
|
|
47
|
+
TestDoubles::AddsTwoAction,
|
|
48
|
+
TestDoubles::AddsThreeAction
|
|
49
|
+
]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'accepts after_actions hook lambdas from organizer' do
|
|
55
|
+
result = AfterActions::AdditionOrganizer.call(0)
|
|
56
|
+
|
|
57
|
+
expect(result.fetch(:number)).to eq(1)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe 'after_actions can be appended' do
|
|
62
|
+
it 'adds to the :_after_actions collection' do
|
|
63
|
+
TestDoubles::AdditionOrganizer.append_after_actions(
|
|
64
|
+
->(ctx) { ctx.number -= 3 if ctx.current_action == TestDoubles::AddsThreeAction }
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
result = TestDoubles::AdditionOrganizer.call(0)
|
|
68
|
+
expect(result.fetch(:number)).to eq(3)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'test_doubles'
|
|
3
|
+
|
|
4
|
+
describe 'Executing arbitrary code around each action' do
|
|
5
|
+
it 'can be used to log data' do
|
|
6
|
+
context = { :number => 0, :logger => TestDoubles::TestLogger.new }
|
|
7
|
+
|
|
8
|
+
result = TestDoubles::AroundEachOrganizer.call(context)
|
|
9
|
+
|
|
10
|
+
expect(result.fetch(:number)).to eq(2)
|
|
11
|
+
expect(result[:logger].logs).to eq(
|
|
12
|
+
[{
|
|
13
|
+
:action => TestDoubles::AddsTwoActionWithFetch,
|
|
14
|
+
:before => 0,
|
|
15
|
+
:after => 2
|
|
16
|
+
}]
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|