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,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,3 @@
1
+ module FunctionalLightService
2
+ VERSION = "0.2.4".freeze
3
+ end
@@ -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