switchyard 7.0.0

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 (129) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/project-build.yml +71 -0
  3. data/.gitignore +24 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +101 -0
  6. data/.solargraph.yml +11 -0
  7. data/Appraisals +7 -0
  8. data/CHANGELOG.md +303 -0
  9. data/CODE_OF_CONDUCT.md +22 -0
  10. data/Gemfile +4 -0
  11. data/LICENSE +22 -0
  12. data/README.md +1663 -0
  13. data/Rakefile +12 -0
  14. data/VERSION +1 -0
  15. data/audit/bench.rb +99 -0
  16. data/audit/verify_findings.rb +172 -0
  17. data/gemfiles/dry_inflector_0_2_1.gemfile +5 -0
  18. data/gemfiles/i18n_1_8_11.gemfile +5 -0
  19. data/lib/switchyard/action.rb +145 -0
  20. data/lib/switchyard/configuration.rb +36 -0
  21. data/lib/switchyard/context/key_verifier.rb +124 -0
  22. data/lib/switchyard/context.rb +208 -0
  23. data/lib/switchyard/deprecations.rb +26 -0
  24. data/lib/switchyard/errors.rb +9 -0
  25. data/lib/switchyard/functional/enum.rb +286 -0
  26. data/lib/switchyard/functional/maybe.rb +21 -0
  27. data/lib/switchyard/functional/monad.rb +77 -0
  28. data/lib/switchyard/functional/null.rb +88 -0
  29. data/lib/switchyard/functional/option.rb +100 -0
  30. data/lib/switchyard/functional/result.rb +129 -0
  31. data/lib/switchyard/functional/sequencer.rb +144 -0
  32. data/lib/switchyard/i18n/localization_adapter.rb +50 -0
  33. data/lib/switchyard/localization_adapter.rb +39 -0
  34. data/lib/switchyard/localization_map.rb +9 -0
  35. data/lib/switchyard/organizer/execute.rb +16 -0
  36. data/lib/switchyard/organizer/iterate.rb +30 -0
  37. data/lib/switchyard/organizer/reduce_case.rb +50 -0
  38. data/lib/switchyard/organizer/reduce_if.rb +19 -0
  39. data/lib/switchyard/organizer/reduce_if_else.rb +23 -0
  40. data/lib/switchyard/organizer/reduce_until.rb +22 -0
  41. data/lib/switchyard/organizer/reduce_while.rb +31 -0
  42. data/lib/switchyard/organizer/scoped_reducable.rb +15 -0
  43. data/lib/switchyard/organizer/with_callback.rb +28 -0
  44. data/lib/switchyard/organizer/with_reducer.rb +81 -0
  45. data/lib/switchyard/organizer/with_reducer_factory.rb +20 -0
  46. data/lib/switchyard/organizer/with_reducer_log_decorator.rb +111 -0
  47. data/lib/switchyard/organizer.rb +129 -0
  48. data/lib/switchyard/testing/context_factory.rb +48 -0
  49. data/lib/switchyard/testing.rb +3 -0
  50. data/lib/switchyard/version.rb +5 -0
  51. data/lib/switchyard.rb +36 -0
  52. data/resources/fail_actions.png +0 -0
  53. data/resources/light-service.png +0 -0
  54. data/resources/organizer_and_actions.png +0 -0
  55. data/resources/skip_actions.png +0 -0
  56. data/spec/acceptance/add_numbers_spec.rb +11 -0
  57. data/spec/acceptance/after_actions_spec.rb +87 -0
  58. data/spec/acceptance/around_each_spec.rb +19 -0
  59. data/spec/acceptance/before_actions_spec.rb +115 -0
  60. data/spec/acceptance/custom_log_from_organizer_spec.rb +61 -0
  61. data/spec/acceptance/deprecation_warnings_spec.rb +82 -0
  62. data/spec/acceptance/fail_spec.rb +52 -0
  63. data/spec/acceptance/log_from_organizer_spec.rb +154 -0
  64. data/spec/acceptance/message_localization_spec.rb +119 -0
  65. data/spec/acceptance/organizer/add_aliases_spec.rb +28 -0
  66. data/spec/acceptance/organizer/add_to_context_spec.rb +54 -0
  67. data/spec/acceptance/organizer/around_each_with_reduce_if_spec.rb +42 -0
  68. data/spec/acceptance/organizer/context_failure_and_skipping_spec.rb +90 -0
  69. data/spec/acceptance/organizer/execute_spec.rb +67 -0
  70. data/spec/acceptance/organizer/iterate_spec.rb +44 -0
  71. data/spec/acceptance/organizer/reduce_case_spec.rb +65 -0
  72. data/spec/acceptance/organizer/reduce_if_else_spec.rb +60 -0
  73. data/spec/acceptance/organizer/reduce_if_spec.rb +89 -0
  74. data/spec/acceptance/organizer/reduce_until_spec.rb +49 -0
  75. data/spec/acceptance/organizer/reduce_while_spec.rb +96 -0
  76. data/spec/acceptance/organizer/with_callback_spec.rb +113 -0
  77. data/spec/acceptance/organizer_entry_point_spec.rb +35 -0
  78. data/spec/acceptance/rollback_spec.rb +183 -0
  79. data/spec/acceptance/skip_all_remaining_spec.rb +139 -0
  80. data/spec/acceptance/testing/context_factory_spec.rb +54 -0
  81. data/spec/action_expected_keys_spec.rb +63 -0
  82. data/spec/action_expects_and_promises_spec.rb +97 -0
  83. data/spec/action_optional_expected_keys_spec.rb +107 -0
  84. data/spec/action_promised_keys_spec.rb +126 -0
  85. data/spec/action_spec.rb +97 -0
  86. data/spec/context/inspect_spec.rb +52 -0
  87. data/spec/context_spec.rb +301 -0
  88. data/spec/examples/amount_spec.rb +77 -0
  89. data/spec/examples/controller_spec.rb +63 -0
  90. data/spec/examples/validate_address_spec.rb +38 -0
  91. data/spec/i18n_localization_adapter_spec.rb +83 -0
  92. data/spec/lib/deterministic/class_mixin_spec.rb +24 -0
  93. data/spec/lib/deterministic/currify_spec.rb +90 -0
  94. data/spec/lib/deterministic/monad_axioms.rb +46 -0
  95. data/spec/lib/deterministic/monad_spec.rb +47 -0
  96. data/spec/lib/deterministic/null_spec.rb +65 -0
  97. data/spec/lib/deterministic/option_spec.rb +140 -0
  98. data/spec/lib/deterministic/result/failure_spec.rb +65 -0
  99. data/spec/lib/deterministic/result/result_map_spec.rb +155 -0
  100. data/spec/lib/deterministic/result/result_shared.rb +25 -0
  101. data/spec/lib/deterministic/result/success_spec.rb +41 -0
  102. data/spec/lib/deterministic/result_spec.rb +63 -0
  103. data/spec/lib/deterministic/sequencer_spec.rb +506 -0
  104. data/spec/lib/edge_cases_spec.rb +156 -0
  105. data/spec/lib/enum_spec.rb +114 -0
  106. data/spec/lib/native_pattern_matching_spec.rb +74 -0
  107. data/spec/localization_adapter_spec.rb +66 -0
  108. data/spec/organizer/with_reducer_spec.rb +56 -0
  109. data/spec/organizer_key_aliases_spec.rb +29 -0
  110. data/spec/organizer_spec.rb +115 -0
  111. data/spec/readme_spec.rb +45 -0
  112. data/spec/sample/calculates_order_tax_action_spec.rb +16 -0
  113. data/spec/sample/calculates_tax_spec.rb +30 -0
  114. data/spec/sample/looks_up_tax_percentage_action_spec.rb +55 -0
  115. data/spec/sample/provides_free_shipping_action_spec.rb +25 -0
  116. data/spec/sample/tax/calculates_order_tax_action.rb +10 -0
  117. data/spec/sample/tax/calculates_tax.rb +11 -0
  118. data/spec/sample/tax/looks_up_tax_percentage_action.rb +28 -0
  119. data/spec/sample/tax/provides_free_shipping_action.rb +11 -0
  120. data/spec/spec_helper.rb +32 -0
  121. data/spec/support.rb +1 -0
  122. data/spec/test_doubles.rb +656 -0
  123. data/spec/testing/context_factory/iterate_spec.rb +39 -0
  124. data/spec/testing/context_factory/reduce_if_spec.rb +40 -0
  125. data/spec/testing/context_factory/reduce_until_spec.rb +40 -0
  126. data/spec/testing/context_factory/with_callback_spec.rb +38 -0
  127. data/spec/testing/context_factory_spec.rb +76 -0
  128. data/switchyard.gemspec +35 -0
  129. metadata +351 -0
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Switchyard
4
+ module Organizer
5
+ class WithCallback
6
+ extend ScopedReducable
7
+
8
+ def self.run(organizer, action, steps)
9
+ ->(ctx) do
10
+ return ctx if ctx.stop_processing?
11
+
12
+ # This will only allow 2 level deep nesting of callbacks
13
+ previous_callback = ctx[:callback]
14
+
15
+ ctx[:callback] = ->(context) do
16
+ ctx = scoped_reduce(organizer, context, steps)
17
+ ctx
18
+ end
19
+
20
+ ctx = action.execute(ctx)
21
+ ctx[:callback] = previous_callback
22
+
23
+ ctx
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Switchyard
4
+ module Organizer
5
+ class WithReducer
6
+ attr_reader :context
7
+ attr_accessor :organizer
8
+
9
+ def initialize(monitored_organizer = nil)
10
+ @organizer = monitored_organizer
11
+ end
12
+
13
+ def with(data = {})
14
+ @context = Switchyard::Context.make(data)
15
+ @context.organized_by = organizer
16
+ self
17
+ end
18
+
19
+ # Handler di default condiviso: prima veniva creata una classe anonima
20
+ # per ogni WithReducer senza around_each
21
+ NOOP_AROUND_EACH_HANDLER = ->(_context, &block) { block.call }
22
+
23
+ def around_each(handler)
24
+ @around_each_handler = handler
25
+ self
26
+ end
27
+
28
+ def around_each_handler
29
+ @around_each_handler || NOOP_AROUND_EACH_HANDLER
30
+ end
31
+
32
+ def reduce(*actions)
33
+ raise "No action(s) were provided" if actions.empty?
34
+
35
+ actions.flatten!
36
+
37
+ actions.each_with_index.with_object(context) do |(action, index), current_context|
38
+ invoke_action(current_context, action)
39
+ rescue FailWithRollbackError
40
+ reduce_rollback(actions, index)
41
+ ensure
42
+ # For logging
43
+ yield(current_context, action) if block_given?
44
+ end
45
+ end
46
+
47
+ def reduce_rollback(actions, index_of_failed_action = nil)
48
+ reversable_actions(actions, index_of_failed_action)
49
+ .reverse
50
+ .reduce(context) do |context, action|
51
+ if action.respond_to?(:rollback)
52
+ action.rollback(context)
53
+ else
54
+ context
55
+ end
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def invoke_action(current_context, action)
62
+ around_each_handler.call(current_context) do
63
+ if action.respond_to?(:call)
64
+ action.call(current_context)
65
+ else
66
+ action.execute(current_context)
67
+ end
68
+ end
69
+ end
70
+
71
+ def reversable_actions(actions, index_of_failed_action = nil)
72
+ # L'indice viene tracciato nel reduce: actions.index troverebbe la prima
73
+ # occorrenza e con azioni duplicate il rollback sarebbe parziale
74
+ index_of_failed_action ||= actions.index(@context.current_action) || 0
75
+
76
+ # Reverse from the point where the fail was triggered
77
+ actions.take(index_of_failed_action + 1)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Switchyard
4
+ module Organizer
5
+ class WithReducerFactory
6
+ def self.make(monitored_organizer)
7
+ logger = monitored_organizer.logger || Switchyard::Configuration.logger
8
+ decorated = WithReducer.new(monitored_organizer)
9
+
10
+ return decorated if logger.nil?
11
+
12
+ WithReducerLogDecorator.new(
13
+ monitored_organizer,
14
+ :decorated => decorated,
15
+ :logger => logger
16
+ )
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Switchyard
4
+ module Organizer
5
+ class WithReducerLogDecorator
6
+ attr_reader :logged, :logger, :decorated, :organizer
7
+
8
+ alias logged? logged
9
+
10
+ def initialize(organizer, logger:, decorated: WithReducer.new)
11
+ @decorated = decorated
12
+ @organizer = organizer
13
+
14
+ decorated.organizer = organizer
15
+
16
+ @logger = logger
17
+ @logged = false
18
+ end
19
+
20
+ def with(data = {})
21
+ logger.info { "[Switchyard] - calling organizer <#{organizer}>" }
22
+
23
+ decorated.with(data)
24
+
25
+ logger.info do
26
+ "[Switchyard] - keys in context: " \
27
+ "#{extract_keys(decorated.context.keys)}"
28
+ end
29
+ self
30
+ end
31
+
32
+ def around_each(handler)
33
+ decorated.around_each(handler)
34
+ self
35
+ end
36
+
37
+ def reduce(*actions)
38
+ decorated.reduce(*actions) do |context, action|
39
+ next context if logged?
40
+
41
+ if has_failure?(context)
42
+ write_failure_log(context, action)
43
+ next context
44
+ end
45
+
46
+ if skip_remaining?(context)
47
+ write_skip_remaining_log(context, action)
48
+ next context
49
+ end
50
+
51
+ write_log(action, context)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def write_log(action, context)
58
+ return unless logger.info?
59
+
60
+ logger.info("[Switchyard] - executing <#{action}>")
61
+ log_expects(action)
62
+ log_promises(action)
63
+ logger.info("[Switchyard] - keys in context: "\
64
+ "#{extract_keys(context.keys)}")
65
+ end
66
+
67
+ def log_expects(action)
68
+ return unless defined?(action.expects) && action.expects.any?
69
+
70
+ logger.info("[Switchyard] - expects: " \
71
+ "#{extract_keys(action.expects)}")
72
+ end
73
+
74
+ def log_promises(action)
75
+ return unless defined?(action.promises) && action.promises.any?
76
+
77
+ logger.info("[Switchyard] - promises: " \
78
+ "#{extract_keys(action.promises)}")
79
+ end
80
+
81
+ def extract_keys(keys)
82
+ keys.map { |key| ":#{key}" }.join(', ')
83
+ end
84
+
85
+ def has_failure?(context)
86
+ context.respond_to?(:failure?) && context.failure?
87
+ end
88
+
89
+ def write_failure_log(context, action)
90
+ logger.warn("[Switchyard] - :-((( <#{action}> has failed...")
91
+ logger.warn("[Switchyard] - context message: #{context.message}")
92
+ @logged = true
93
+ end
94
+
95
+ def skip_remaining?(context)
96
+ (context.respond_to?(:skip_remaining?) && context.skip_remaining?) ||
97
+ (context.respond_to?(:skip_all_remaining?) && context.skip_all_remaining?)
98
+ end
99
+
100
+ def write_skip_remaining_log(context, action)
101
+ return unless logger.info?
102
+
103
+ msg = "[Switchyard] - ;-) <#{action}> has decided " \
104
+ "to skip the rest of the actions"
105
+ logger.info(msg)
106
+ logger.info("[Switchyard] - context message: #{context.message}")
107
+ @logged = true
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Switchyard
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
+ Switchyard::Deprecations.warn(
12
+ "Including Switchyard::Organizer is deprecated; " \
13
+ "use `extend Switchyard::Organizer` instead"
14
+ )
15
+ extended(base_class)
16
+ end
17
+
18
+ # In case this module is included
19
+ module ClassMethods
20
+ def with(data = {})
21
+ data[:_aliases] = @aliases if @aliases
22
+
23
+ # Gli hook di classe vengono solo letti (mai azzerati): devono valere
24
+ # per ogni chiamata, anche concorrente
25
+ data[:_before_actions] = @before_actions.dup if @before_actions
26
+ data[:_after_actions] = @after_actions.dup if @after_actions
27
+
28
+ WithReducerFactory.make(self).with(data)
29
+ end
30
+
31
+ def reduce(*actions)
32
+ with({}).reduce(actions)
33
+ end
34
+
35
+ def reduce_if(condition_block, steps)
36
+ ReduceIf.run(self, condition_block, steps)
37
+ end
38
+
39
+ def reduce_if_else(condition_block, if_steps, else_steps)
40
+ ReduceIfElse.run(self, condition_block, if_steps, else_steps)
41
+ end
42
+
43
+ def reduce_until(condition_block, steps)
44
+ ReduceUntil.run(self, condition_block, steps)
45
+ end
46
+
47
+ def reduce_case(**args)
48
+ ReduceCase.run(self, **args)
49
+ end
50
+
51
+ def reduce_while(condition_block, steps)
52
+ ReduceWhile.run(self, condition_block, steps)
53
+ end
54
+
55
+ def iterate(collection_key, steps)
56
+ Iterate.run(self, collection_key, steps)
57
+ end
58
+
59
+ def execute(code_block = nil, &block)
60
+ Execute.run(code_block || block)
61
+ end
62
+
63
+ def with_callback(action, steps)
64
+ WithCallback.run(self, action, steps)
65
+ end
66
+
67
+ def log_with(logger)
68
+ @logger = logger
69
+ end
70
+
71
+ def logger
72
+ @logger
73
+ end
74
+
75
+ def add_to_context(**args)
76
+ args.map do |key, value|
77
+ execute(->(ctx) do
78
+ ctx[key.to_sym] = value
79
+ ctx.define_accessor_methods_for_keys([key])
80
+ end)
81
+ end
82
+ end
83
+
84
+ def add_aliases(args)
85
+ execute(->(ctx) { ctx.assign_aliases(ctx.aliases.merge(args)) })
86
+ end
87
+ end
88
+
89
+ module Macros
90
+ def aliases(key_hash)
91
+ @aliases = key_hash
92
+ end
93
+
94
+ # This looks like an accessor,
95
+ # but it's used as a macro in the Organizer
96
+ def before_actions(*logic)
97
+ self.before_actions = logic
98
+ end
99
+
100
+ def before_actions=(logic)
101
+ @before_actions = logic.nil? ? nil : [logic].flatten
102
+ end
103
+
104
+ def append_before_actions(action)
105
+ @before_actions ||= []
106
+ @before_actions.push(action)
107
+ end
108
+
109
+ def remove_before_actions(action)
110
+ @before_actions&.delete(action)
111
+ end
112
+
113
+ # This looks like an accessor,
114
+ # but it's used as a macro in the Organizer
115
+ def after_actions(*logic)
116
+ self.after_actions = logic
117
+ end
118
+
119
+ def after_actions=(logic)
120
+ @after_actions = logic.nil? ? nil : [logic].flatten
121
+ end
122
+
123
+ def append_after_actions(action)
124
+ @after_actions ||= []
125
+ @after_actions.push(action)
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Switchyard
4
+ module Testing
5
+ class ContextFactory
6
+ attr_reader :organizer
7
+
8
+ def self.make_from(organizer)
9
+ new(organizer)
10
+ end
11
+
12
+ def for(action)
13
+ @target_action = action
14
+ self
15
+ end
16
+
17
+ # More than one arguments can be passed to the
18
+ # Organizer's #call method
19
+ def with(...)
20
+ hook = nil
21
+ hook = ->(ctx) do
22
+ if ctx.current_action == @target_action
23
+ # L'hook non deve essere re-invocato quando il context
24
+ # verra' usato con Action#execute nel test
25
+ ctx[:_before_actions].delete(hook)
26
+
27
+ throw(:return_ctx_from_execution, ctx)
28
+ end
29
+ end
30
+
31
+ @organizer.append_before_actions(hook)
32
+
33
+ begin
34
+ catch(:return_ctx_from_execution) do
35
+ @organizer.call(...)
36
+ end
37
+ ensure
38
+ # L'hook e' per-chiamata: la classe organizer non deve conservarlo
39
+ @organizer.remove_before_actions(hook)
40
+ end
41
+ end
42
+
43
+ def initialize(organizer)
44
+ @organizer = organizer
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'switchyard/testing/context_factory'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Switchyard
4
+ VERSION = "7.0.0"
5
+ end
data/lib/switchyard.rb ADDED
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ require 'switchyard/version'
6
+
7
+ module Switchyard; end
8
+
9
+ require 'switchyard/deprecations'
10
+ require 'switchyard/functional/monad'
11
+ require 'switchyard/functional/enum'
12
+ require 'switchyard/functional/result'
13
+ require 'switchyard/functional/option'
14
+ require 'switchyard/functional/null'
15
+ require 'switchyard/functional/sequencer'
16
+ require 'switchyard/errors'
17
+ require 'switchyard/configuration'
18
+ require 'switchyard/i18n/localization_adapter'
19
+ require 'switchyard/localization_adapter'
20
+ require 'switchyard/localization_map'
21
+ require 'switchyard/context'
22
+ require 'switchyard/context/key_verifier'
23
+ require 'switchyard/organizer/scoped_reducable'
24
+ require 'switchyard/organizer/with_reducer'
25
+ require 'switchyard/organizer/with_reducer_log_decorator'
26
+ require 'switchyard/organizer/with_reducer_factory'
27
+ require 'switchyard/organizer/reduce_if'
28
+ require 'switchyard/organizer/reduce_if_else'
29
+ require 'switchyard/organizer/reduce_case'
30
+ require 'switchyard/organizer/reduce_until'
31
+ require 'switchyard/organizer/reduce_while'
32
+ require 'switchyard/organizer/iterate'
33
+ require 'switchyard/organizer/execute'
34
+ require 'switchyard/organizer/with_callback'
35
+ require 'switchyard/action'
36
+ require 'switchyard/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,87 @@
1
+ require 'spec_helper'
2
+ require 'test_doubles'
3
+
4
+ RSpec.describe 'Action after_actions' do
5
+ # Gli hook restano sulla classe (non vengono piu consumati da #with):
6
+ # i test double sono condivisi, quindi vanno ripuliti dopo ogni esempio
7
+ after do
8
+ TestDoubles::AdditionOrganizer.after_actions = nil
9
+ TestDoubles::TestIterate.after_actions = nil
10
+ end
11
+
12
+ describe 'works with simple organizers - from outside' do
13
+ it 'can be used to inject code block before each action' do
14
+ TestDoubles::AdditionOrganizer.after_actions = ->(ctx) do
15
+ ctx.number -= 2 if ctx.current_action == TestDoubles::AddsThreeAction
16
+ end
17
+
18
+ result = TestDoubles::AdditionOrganizer.call(0)
19
+
20
+ expect(result.fetch(:number)).to eq(4)
21
+ end
22
+
23
+ it 'works with iterator' do
24
+ TestDoubles::TestIterate.after_actions = [
25
+ ->(ctx) { ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction }
26
+ ]
27
+
28
+ result = TestDoubles::TestIterate.call(:number => 0,
29
+ :counters => [1, 2, 3, 4])
30
+
31
+ expect(result).to be_success
32
+ expect(result.number).to eq(-4)
33
+ end
34
+ end
35
+
36
+ describe 'can be added to organizers declaratively' do
37
+ module AfterActions
38
+ class AdditionOrganizer
39
+ extend Switchyard::Organizer
40
+
41
+ after_actions ->(ctx) do
42
+ ctx.number -= 2 if ctx.current_action == TestDoubles::AddsOneAction
43
+ end,
44
+ ->(ctx) do
45
+ ctx.number -= 3 if ctx.current_action == TestDoubles::AddsThreeAction
46
+ end
47
+
48
+ def self.call(number)
49
+ with(:number => number).reduce(actions)
50
+ end
51
+
52
+ def self.actions
53
+ [
54
+ TestDoubles::AddsOneAction,
55
+ TestDoubles::AddsTwoAction,
56
+ TestDoubles::AddsThreeAction
57
+ ]
58
+ end
59
+ end
60
+ end
61
+
62
+ it 'accepts after_actions hook lambdas from organizer' do
63
+ result = AfterActions::AdditionOrganizer.call(0)
64
+
65
+ expect(result.fetch(:number)).to eq(1)
66
+ end
67
+
68
+ it 'keeps the declarative hooks on every subsequent call' do
69
+ first = AfterActions::AdditionOrganizer.call(0)
70
+ second = AfterActions::AdditionOrganizer.call(0)
71
+
72
+ expect(first.fetch(:number)).to eq(1)
73
+ expect(second.fetch(:number)).to eq(1)
74
+ end
75
+ end
76
+
77
+ describe 'after_actions can be appended' do
78
+ it 'adds to the :_after_actions collection' do
79
+ TestDoubles::AdditionOrganizer.append_after_actions(
80
+ ->(ctx) { ctx.number -= 3 if ctx.current_action == TestDoubles::AddsThreeAction }
81
+ )
82
+
83
+ result = TestDoubles::AdditionOrganizer.call(0)
84
+ expect(result.fetch(:number)).to eq(3)
85
+ end
86
+ end
87
+ 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