activeinteractor 1.0.0.beta.7 → 1.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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +12 -0
  3. data/CHANGELOG.md +50 -158
  4. data/README.md +13 -856
  5. data/lib/active_interactor.rb +61 -4
  6. data/lib/active_interactor/base.rb +26 -20
  7. data/lib/active_interactor/config.rb +36 -18
  8. data/lib/active_interactor/configurable.rb +17 -9
  9. data/lib/active_interactor/context/attributes.rb +73 -26
  10. data/lib/active_interactor/context/base.rb +236 -65
  11. data/lib/active_interactor/context/loader.rb +20 -15
  12. data/lib/active_interactor/context/status.rb +38 -56
  13. data/lib/active_interactor/error.rb +15 -7
  14. data/lib/active_interactor/interactor/callbacks.rb +174 -160
  15. data/lib/active_interactor/interactor/context.rb +279 -87
  16. data/lib/active_interactor/interactor/perform.rb +256 -0
  17. data/lib/active_interactor/interactor/worker.rb +19 -14
  18. data/lib/active_interactor/models.rb +65 -0
  19. data/lib/active_interactor/organizer/base.rb +18 -0
  20. data/lib/active_interactor/organizer/callbacks.rb +153 -0
  21. data/lib/active_interactor/organizer/interactor_interface.rb +38 -26
  22. data/lib/active_interactor/organizer/interactor_interface_collection.rb +35 -32
  23. data/lib/active_interactor/organizer/organize.rb +67 -0
  24. data/lib/active_interactor/organizer/perform.rb +93 -0
  25. data/lib/active_interactor/rails.rb +0 -10
  26. data/lib/active_interactor/rails/orm/active_record.rb +1 -1
  27. data/lib/active_interactor/rails/railtie.rb +8 -11
  28. data/lib/active_interactor/version.rb +2 -1
  29. data/lib/rails/generators/active_interactor.rb +1 -38
  30. data/lib/rails/generators/active_interactor/application_context_generator.rb +21 -0
  31. data/lib/rails/generators/active_interactor/application_interactor_generator.rb +5 -15
  32. data/lib/rails/generators/active_interactor/application_organizer_generator.rb +21 -0
  33. data/lib/rails/generators/active_interactor/base.rb +29 -0
  34. data/lib/rails/generators/active_interactor/generator.rb +21 -0
  35. data/lib/rails/generators/active_interactor/install_generator.rb +2 -9
  36. data/lib/rails/generators/interactor/context/rspec_generator.rb +3 -10
  37. data/lib/rails/generators/interactor/context/test_unit_generator.rb +4 -11
  38. data/lib/rails/generators/interactor/context_generator.rb +7 -10
  39. data/lib/rails/generators/interactor/generates_context.rb +28 -0
  40. data/lib/rails/generators/interactor/interactor_generator.rb +8 -10
  41. data/lib/rails/generators/interactor/organizer_generator.rb +8 -12
  42. data/lib/rails/generators/interactor/rspec_generator.rb +2 -9
  43. data/lib/rails/generators/interactor/test_unit_generator.rb +3 -10
  44. data/lib/rails/generators/{active_interactor/templates/initializer.erb → templates/active_interactor.erb} +3 -11
  45. data/lib/rails/generators/{active_interactor/templates → templates}/application_context.rb +0 -0
  46. data/lib/rails/generators/{active_interactor/templates → templates}/application_interactor.rb +0 -0
  47. data/lib/rails/generators/templates/application_organizer.rb +4 -0
  48. data/lib/rails/generators/{interactor/templates → templates}/context.erb +0 -0
  49. data/lib/rails/generators/{interactor/context/templates/rspec.erb → templates/context_spec.erb} +0 -0
  50. data/lib/rails/generators/{interactor/context/templates/test_unit.erb → templates/context_test_unit.erb} +0 -0
  51. data/lib/rails/generators/{interactor/templates → templates}/interactor.erb +0 -0
  52. data/lib/rails/generators/{interactor/templates/rspec.erb → templates/interactor_spec.erb} +0 -0
  53. data/lib/rails/generators/{interactor/templates/test_unit.erb → templates/interactor_text_unit.erb} +0 -0
  54. data/lib/rails/generators/{interactor/templates → templates}/organizer.erb +0 -0
  55. data/spec/active_interactor/base_spec.rb +3 -3
  56. data/spec/active_interactor/interactor/{perform_options_spec.rb → perform/options_spec.rb} +1 -1
  57. data/spec/active_interactor/interactor/worker_spec.rb +14 -15
  58. data/spec/active_interactor/{organizer_spec.rb → organizer/base_spec.rb} +27 -17
  59. data/spec/integration/a_basic_interactor_spec.rb +106 -0
  60. data/spec/integration/a_basic_organizer_spec.rb +97 -0
  61. data/spec/integration/a_failing_interactor_spec.rb +42 -0
  62. data/spec/integration/active_record_integration_spec.rb +9 -9
  63. data/spec/integration/an_interactor_with_after_context_validation_callbacks_spec.rb +69 -0
  64. data/spec/integration/an_interactor_with_after_perform_callbacks_spec.rb +30 -0
  65. data/spec/integration/an_interactor_with_after_rollback_callbacks_spec.rb +33 -0
  66. data/spec/integration/an_interactor_with_an_existing_context_class_spec.rb +49 -0
  67. data/spec/integration/an_interactor_with_around_perform_callbacks_spec.rb +35 -0
  68. data/spec/integration/an_interactor_with_around_rollback_callbacks_spec.rb +39 -0
  69. data/spec/integration/an_interactor_with_before_perform_callbacks_spec.rb +30 -0
  70. data/spec/integration/an_interactor_with_before_rollback_callbacks_spec.rb +33 -0
  71. data/spec/integration/an_interactor_with_validations_on_called_spec.rb +40 -0
  72. data/spec/integration/an_interactor_with_validations_on_calling_spec.rb +36 -0
  73. data/spec/integration/an_interactor_with_validations_spec.rb +93 -0
  74. data/spec/integration/an_organizer_performing_in_parallel_spec.rb +48 -0
  75. data/spec/integration/an_organizer_with_after_each_callbacks_spec.rb +34 -0
  76. data/spec/integration/an_organizer_with_around_each_callbacks_spec.rb +39 -0
  77. data/spec/integration/an_organizer_with_before_each_callbacks_spec.rb +34 -0
  78. data/spec/integration/an_organizer_with_conditionally_organized_interactors_spec.rb +314 -0
  79. data/spec/spec_helper.rb +8 -12
  80. data/spec/support/coverage.rb +4 -0
  81. data/spec/support/coverage/reporters.rb +11 -0
  82. data/spec/support/coverage/reporters/codacy.rb +39 -0
  83. data/spec/support/coverage/reporters/simple_cov.rb +54 -0
  84. data/spec/support/coverage/runner.rb +66 -0
  85. data/spec/support/helpers/factories.rb +1 -1
  86. data/spec/support/shared_examples/a_class_with_interactor_callback_methods_example.rb +8 -8
  87. data/spec/support/shared_examples/a_class_with_interactor_context_methods_example.rb +5 -5
  88. data/spec/support/shared_examples/a_class_with_interactor_methods_example.rb +2 -2
  89. data/spec/support/shared_examples/a_class_with_organizer_callback_methods_example.rb +3 -3
  90. metadata +83 -40
  91. data/lib/active_interactor/interactor.rb +0 -84
  92. data/lib/active_interactor/interactor/perform_options.rb +0 -29
  93. data/lib/active_interactor/organizer.rb +0 -269
  94. data/lib/active_interactor/rails/config.rb +0 -45
  95. data/lib/active_interactor/rails/models.rb +0 -58
  96. data/lib/rails/generators/active_interactor/templates/application_organizer.rb +0 -4
  97. data/spec/active_interactor/rails/config_spec.rb +0 -29
  98. data/spec/active_interactor/rails_spec.rb +0 -24
  99. data/spec/integration/basic_callback_integration_spec.rb +0 -355
  100. data/spec/integration/basic_context_integration_spec.rb +0 -73
  101. data/spec/integration/basic_integration_spec.rb +0 -570
  102. data/spec/integration/basic_validations_integration_spec.rb +0 -204
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveInteractor
4
- # Interactor methods. {Base} includes {Interactor} all interactors
5
- # should inherit from {Base}.
6
- # @author Aaron Allen <hello@aaronmallen.me>
7
- # @since 0.0.1
8
- module Interactor
9
- def self.included(base)
10
- base.class_eval do
11
- extend ClassMethods
12
- include Callbacks
13
- include Context
14
- delegate :execute_perform, :execute_perform!, to: :worker
15
- end
16
- end
17
-
18
- # Interactor class methods.
19
- module ClassMethods
20
- # Run an interactor context. This it the primary API method for
21
- # interacting with interactors.
22
- # @example Run an interactor
23
- # MyInteractor.perform(name: 'Aaron')
24
- # #=> <#MyInteractor::Context name='Aaron'>
25
- # @param context [Hash|Context::Base] attributes to assign to the interactor context
26
- # @param options [Hash] execution options for the interactor perform step
27
- # see {PerformOptions}
28
- # @return [Context::Base] an instance of context.
29
- def perform(context = {}, options = {})
30
- new(context).with_options(options).execute_perform
31
- end
32
-
33
- # Run an interactor context. The {.perform!} method behaves identically to
34
- # the {.perform} method with one notable exception. If the context is failed
35
- # during invocation of the interactor, the {Error::ContextFailure}
36
- # is raised.
37
- # @example Run an interactor
38
- # MyInteractor.perform!(name: 'Aaron')
39
- # #=> <#MyInteractor::Context name='Aaron'>
40
- # @param context [Hash|Context::Base] attributes to assign to the interactor context
41
- # @param options [Hash] execution options for the interactor perform step
42
- # see {PerformOptions}
43
- # @raise [Error::ContextFailure] if the context fails.
44
- # @return [Context::Base] an instance of context.
45
- def perform!(context = {}, options = {})
46
- new(context).with_options(options).execute_perform!
47
- end
48
- end
49
-
50
- # Options for invokation of {#perform}
51
- # @since 1.0.0
52
- # @return [PerformOptions] the options
53
- def options
54
- @options ||= PerformOptions.new
55
- end
56
-
57
- # Invoke an Interactor instance without any hooks, tracking, or rollback
58
- # @abstract It is expected that the {#perform} method is overwritten
59
- # for each interactor class.
60
- def perform; end
61
-
62
- # Reverse prior invocation of an Interactor instance.
63
- # @abstract Any interactor class that requires undoing upon downstream
64
- # failure is expected to overwrite the {#rollback} method.
65
- def rollback; end
66
-
67
- # Set options for invokation of {#perform}
68
- # @since 1.0.0
69
- # @param options [PerformOptions|Hash] the perform options
70
- # @return [Base] the instance of {Base}
71
- def with_options(options)
72
- @options = options.is_a?(PerformOptions) ? options : PerformOptions.new(options)
73
- self
74
- end
75
-
76
- private
77
-
78
- def worker
79
- Worker.new(self)
80
- end
81
- end
82
- end
83
-
84
- Dir[File.expand_path('interactor/*.rb', __dir__)].sort.each { |file| require file }
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveInteractor
4
- module Interactor
5
- # Options object for interactor perform
6
- # @author Aaron Allen <hello@aaronmallen.me>
7
- # @since 1.0.0
8
- # @!attribute [rw] skip_each_perform_callbacks
9
- # @return [Boolean] whether or not to skip :each_perform callbacks for an {Organizer}
10
- # @!attribute [rw] skip_perform_callbacks
11
- # @return [Boolean] whether or not to skip :perform callbacks
12
- # @!attribute [rw] skip_rollback
13
- # @return [Boolean] whether or not to skip rollback when an interactor
14
- # fails its context
15
- # @!attribute [rw] skip_rollback_callbacks
16
- # @return [Boolean] whether or not to skip :rollback callbacks
17
- # @!attribute [rw] validate
18
- # @return [Boolean] whether or not to run validations on an interactor
19
- # @!attribute [rw] validate_on_calling
20
- # @return [Boolean] whether or not to run validation on :calling
21
- # @!attribute [rw] validate_on_called
22
- # @return [Boolean] whether or not to run validation on :called
23
- class PerformOptions
24
- include ActiveInteractor::Configurable
25
- defaults skip_each_perform_callbacks: false, skip_perform_callbacks: false, skip_rollback: false,
26
- skip_rollback_callbacks: false, validate: true, validate_on_calling: true, validate_on_called: true
27
- end
28
- end
29
- end
@@ -1,269 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/core_ext/class/attribute'
4
-
5
- require 'active_interactor/organizer/interactor_interface_collection'
6
-
7
- module ActiveInteractor
8
- # A base Organizer class. All organizers should inherit from
9
- # {Organizer}.
10
- # @author Aaron Allen <hello@aaronmallen.me>
11
- # @since 0.0.1
12
- # @!attribute [r] parallel
13
- # @since 1.0.0
14
- # @!scope class
15
- # @return [Boolean] whether or not to run the interactors
16
- # in parallel
17
- # @example a basic organizer
18
- # class MyInteractor1 < ActiveInteractor::Base
19
- # def perform
20
- # context.interactor1 = true
21
- # end
22
- # end
23
- #
24
- # class MyInteractor2 < ActiveInteractor::Base
25
- # def perform
26
- # context.interactor2 = true
27
- # end
28
- # end
29
- #
30
- # class MyOrganizer < ActiveInteractor::Organizer
31
- # organize MyInteractor1, MyInteractor2
32
- # end
33
- #
34
- # MyOrganizer.perform
35
- # #=> <MyOrganizer::Context interactor1=true interactor2=true>
36
- class Organizer < Base
37
- class_attribute :parallel, instance_writer: false, default: false
38
- define_callbacks :each_perform
39
-
40
- # Define a callback to call after each organized interactor's
41
- # {Base.perform} has been invoked
42
- # @example
43
- # class MyInteractor1 < ActiveInteractor::Base
44
- # before_perform :print_name
45
- #
46
- # def perform
47
- # puts 'MyInteractor1'
48
- # end
49
- # end
50
- #
51
- # class MyInteractor2 < ActiveInteractor::Base
52
- # before_perform :print_name
53
- #
54
- # def perform
55
- # puts 'MyInteractor2'
56
- # end
57
- # end
58
- #
59
- # class MyOrganizer < ActiveInteractor::Organizer
60
- # after_each_perform :print_done
61
- #
62
- # organized MyInteractor1, MyInteractor2
63
- #
64
- # private
65
- #
66
- # def print_done
67
- # puts "done"
68
- # end
69
- # end
70
- #
71
- # MyOrganizer.perform(name: 'Aaron')
72
- # "MyInteractor1"
73
- # "Done"
74
- # "MyInteractor2"
75
- # "Done"
76
- # #=> <MyOrganizer::Context name='Aaron'>
77
- def self.after_each_perform(*filters, &block)
78
- set_callback(:each_perform, :after, *filters, &block)
79
- end
80
-
81
- # Define a callback to call around each organized interactor's
82
- # {Base.perform} has been invokation
83
- # @example
84
- # class MyInteractor1 < ActiveInteractor::Base
85
- # before_perform :print_name
86
- #
87
- # def perform
88
- # puts 'MyInteractor1'
89
- # end
90
- # end
91
- #
92
- # class MyInteractor2 < ActiveInteractor::Base
93
- # before_perform :print_name
94
- #
95
- # def perform
96
- # puts 'MyInteractor2'
97
- # end
98
- # end
99
- #
100
- # class MyOrganizer < ActiveInteractor::Organizer
101
- # around_each_perform :print_time
102
- #
103
- # organized MyInteractor1, MyInteractor2
104
- #
105
- # private
106
- #
107
- # def print_time
108
- # puts Time.now.utc
109
- # yield
110
- # puts Time.now.utc
111
- # end
112
- # end
113
- #
114
- # MyOrganizer.perform(name: 'Aaron')
115
- # "2019-04-01 00:00:00 UTC"
116
- # "MyInteractor1"
117
- # "2019-04-01 00:00:01 UTC"
118
- # "2019-04-01 00:00:02 UTC"
119
- # "MyInteractor2"
120
- # "2019-04-01 00:00:03 UTC"
121
- # #=> <MyOrganizer::Context name='Aaron'>
122
- def self.around_each_perform(*filters, &block)
123
- set_callback(:each_perform, :around, *filters, &block)
124
- end
125
-
126
- # Define a callback to call before each organized interactor's
127
- # {Base.perform} has been invoked
128
- # @example
129
- # class MyInteractor1 < ActiveInteractor::Base
130
- # before_perform :print_name
131
- #
132
- # def perform
133
- # puts 'MyInteractor1'
134
- # end
135
- # end
136
- #
137
- # class MyInteractor2 < ActiveInteractor::Base
138
- # before_perform :print_name
139
- #
140
- # def perform
141
- # puts 'MyInteractor2'
142
- # end
143
- # end
144
- #
145
- # class MyOrganizer < ActiveInteractor::Organizer
146
- # before_each_perform :print_start
147
- #
148
- # organized MyInteractor1, MyInteractor2
149
- #
150
- # private
151
- #
152
- # def print_start
153
- # puts "Start"
154
- # end
155
- # end
156
- #
157
- # MyOrganizer.perform(name: 'Aaron')
158
- # "Start"
159
- # "MyInteractor1"
160
- # "Start"
161
- # "MyInteractor2"
162
- # #=> <MyOrganizer::Context name='Aaron'>
163
- def self.before_each_perform(*filters, &block)
164
- set_callback(:each_perform, :before, *filters, &block)
165
- end
166
-
167
- # Declare Interactors to be invoked as part of the
168
- # organizer's invocation. These interactors are invoked in
169
- # the order in which they are declared
170
- # @example Basic interactor organization
171
- # class MyOrganizer < ActiveInteractor::Organizer
172
- # organize :interactor_one, :interactor_two
173
- # end
174
- # @example Conditional interactor organization with block
175
- # class MyOrganizer < ActiveInteractor::Organizer
176
- # organize do
177
- # add :interactor_one
178
- # add :interactor_two, if: -> { context.valid? }
179
- # end
180
- # end
181
- # @example Conditional interactor organization with method
182
- # class MyOrganizer < ActiveInteractor::Organizer
183
- # organize do
184
- # add :interactor_one
185
- # add :interactor_two, unless: :invalid_context?
186
- # end
187
- #
188
- # private
189
- #
190
- # def invalid_context?
191
- # !context.valid?
192
- # end
193
- # end
194
- # @example Interactor organization with perform options
195
- # class MyOrganizer < ActiveInteractor::Organizer
196
- # organize do
197
- # add :interactor_one, validate: false
198
- # add :interactor_two, skip_perform_callbacks: true
199
- # end
200
- # end
201
- # @param interactors [Array<Base|Symbol|String>] the interactors to call
202
- # @yield [.organized] if block given
203
- # @return [InteractorInterfaceCollection] an instance of {InteractorInterfaceCollection}
204
- def self.organize(*interactors, &block)
205
- organized.concat(interactors) if interactors
206
- organized.instance_eval(&block) if block
207
- organized
208
- end
209
-
210
- # Organized interactors
211
- # @return [InteractorInterfaceCollection] an instance of {InteractorInterfaceCollection}
212
- def self.organized
213
- @organized ||= InteractorInterfaceCollection.new
214
- end
215
-
216
- # Run organized interactors in parallel
217
- # @since 1.0.0
218
- def self.perform_in_parallel
219
- self.parallel = true
220
- end
221
-
222
- # Invoke the organized interactors. An organizer is
223
- # expected not to define its own {Interactor#perform #perform} method
224
- # in favor of this default implementation.
225
- def perform
226
- if self.class.parallel
227
- perform_in_parallel
228
- else
229
- perform_in_order
230
- end
231
- end
232
-
233
- private
234
-
235
- def execute_interactor(interface, fail_on_error = false, perform_options = {})
236
- interface.perform(self, context, fail_on_error, perform_options)
237
- end
238
-
239
- def execute_interactor_with_callbacks(interface, fail_on_error = false, perform_options = {})
240
- args = [interface, fail_on_error, perform_options]
241
- return execute_interactor(*args) if options.skip_each_perform_callbacks
242
-
243
- run_callbacks :each_perform do
244
- execute_interactor(*args)
245
- end
246
- end
247
-
248
- def merge_contexts(contexts)
249
- contexts.each { |context| @context.merge!(context) }
250
- context_fail! if contexts.any?(&:failure?)
251
- end
252
-
253
- def perform_in_order
254
- self.class.organized.each do |interface|
255
- result = execute_interactor_with_callbacks(interface, true)
256
- context.merge!(result) if result
257
- end
258
- rescue Error::ContextFailure => e
259
- context.merge!(e.context)
260
- end
261
-
262
- def perform_in_parallel
263
- results = self.class.organized.map do |interface|
264
- Thread.new { execute_interactor_with_callbacks(interface, false, skip_rollback: true) }
265
- end
266
- merge_contexts(results.map(&:value))
267
- end
268
- end
269
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_interactor/configurable'
4
-
5
- module ActiveInteractor
6
- module Rails
7
- # The ActiveInteractor rails configuration object
8
- # @author Aaron Allen <hello@aaronmallen.me>
9
- # @since 1.0.0
10
- # @!attribute [rw] directory
11
- # @return [String] the directory interactors are stored in
12
- # @!attribute [rw] generate_context_classes
13
- # @return [Boolean] whether or not to generate a seperate
14
- # context class for an interactor.
15
- class Config
16
- include ActiveInteractor::Configurable
17
- defaults directory: 'interactors', generate_context_classes: true
18
-
19
- # @!method initialize(options = {})
20
- # @param options [Hash] the options for the configuration
21
- # @option options [String] :directory (defaults to: 'interactors') the configuration directory property
22
- # @option options [Boolean] :generate_context_classes (defaults to: `true`) the configuration
23
- # generate_context_class property.
24
- # @return [Config] a new instance of {Config}
25
- end
26
-
27
- # The {ActiveInteractor::Rails} configuration
28
- # @since 1.0.0
29
- # @return [ActiveInteractor::Config] the configuration instance
30
- def self.config
31
- @config ||= Config.new
32
- end
33
-
34
- # Configures {ActiveInteractor::Rails}
35
- # @since 1.0.0
36
- # @example Configure ActiveInteractor
37
- # ActiveInteractor.configure do |config|
38
- # config.logger = Rails.logger
39
- # end
40
- # @yield [ActiveInteractor::Rails#config]
41
- def self.configure
42
- yield config
43
- end
44
- end
45
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveInteractor
4
- module Rails
5
- # Model helper methods
6
- # @author Aaron Allen <hello@aaronmallen.me>
7
- # @since 1.0.0
8
- module Models
9
- # Model class helper methods
10
- # @author Aaron Allen <hello@aaronmallen.me>
11
- # @since 1.0.0
12
- module ClassMethods
13
- # Include {Context::Status} methods
14
- def acts_as_context
15
- class_eval do
16
- include InstanceMethods
17
- include ActiveInteractor::Context::Status
18
- delegate :each_pair, to: :attributes
19
- end
20
- end
21
- end
22
-
23
- # Model instance helper methods
24
- # @author Aaron Allen <hello@aaronmallen.me>
25
- # @since 1.0.0
26
- module InstanceMethods
27
- # Override initialize method to ensure
28
- # context flags are copied to the new instance
29
- # @param attributes [Hash|nil] attributes to assign to the class
30
- def initialize(attributes = nil)
31
- copy_flags!(attributes) if attributes
32
- copy_called!(attributes) if attributes
33
- attributes_as_hash = attributes_as_hash(attributes)
34
- super(attributes_as_hash)
35
- end
36
-
37
- # Merge an instance and ensure
38
- # context flags are copied to the new instance
39
- # @param context [*] the instance to be merged
40
- # @return [*] the merged instance
41
- def merge!(context)
42
- copy_flags!(context)
43
- context.each_pair do |key, value|
44
- self[key] = value
45
- end
46
- self
47
- end
48
-
49
- private
50
-
51
- def attributes_as_hash(attributes)
52
- return attributes.to_h if attributes&.respond_to?(:to_h)
53
- return attributes.attributes.to_h if attributes.respond_to?(:attributes)
54
- end
55
- end
56
- end
57
- end
58
- end