activeinteractor 1.0.0.beta.7 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +12 -0
- data/CHANGELOG.md +50 -158
- data/README.md +13 -856
- data/lib/active_interactor.rb +61 -4
- data/lib/active_interactor/base.rb +26 -20
- data/lib/active_interactor/config.rb +36 -18
- data/lib/active_interactor/configurable.rb +17 -9
- data/lib/active_interactor/context/attributes.rb +73 -26
- data/lib/active_interactor/context/base.rb +236 -65
- data/lib/active_interactor/context/loader.rb +20 -15
- data/lib/active_interactor/context/status.rb +38 -56
- data/lib/active_interactor/error.rb +15 -7
- data/lib/active_interactor/interactor/callbacks.rb +174 -160
- data/lib/active_interactor/interactor/context.rb +279 -87
- data/lib/active_interactor/interactor/perform.rb +256 -0
- data/lib/active_interactor/interactor/worker.rb +19 -14
- data/lib/active_interactor/models.rb +65 -0
- data/lib/active_interactor/organizer/base.rb +18 -0
- data/lib/active_interactor/organizer/callbacks.rb +153 -0
- data/lib/active_interactor/organizer/interactor_interface.rb +38 -26
- data/lib/active_interactor/organizer/interactor_interface_collection.rb +35 -32
- data/lib/active_interactor/organizer/organize.rb +67 -0
- data/lib/active_interactor/organizer/perform.rb +93 -0
- data/lib/active_interactor/rails.rb +0 -10
- data/lib/active_interactor/rails/orm/active_record.rb +1 -1
- data/lib/active_interactor/rails/railtie.rb +8 -11
- data/lib/active_interactor/version.rb +2 -1
- data/lib/rails/generators/active_interactor.rb +1 -38
- data/lib/rails/generators/active_interactor/application_context_generator.rb +21 -0
- data/lib/rails/generators/active_interactor/application_interactor_generator.rb +5 -15
- data/lib/rails/generators/active_interactor/application_organizer_generator.rb +21 -0
- data/lib/rails/generators/active_interactor/base.rb +29 -0
- data/lib/rails/generators/active_interactor/generator.rb +21 -0
- data/lib/rails/generators/active_interactor/install_generator.rb +2 -9
- data/lib/rails/generators/interactor/context/rspec_generator.rb +3 -10
- data/lib/rails/generators/interactor/context/test_unit_generator.rb +4 -11
- data/lib/rails/generators/interactor/context_generator.rb +7 -10
- data/lib/rails/generators/interactor/generates_context.rb +28 -0
- data/lib/rails/generators/interactor/interactor_generator.rb +8 -10
- data/lib/rails/generators/interactor/organizer_generator.rb +8 -12
- data/lib/rails/generators/interactor/rspec_generator.rb +2 -9
- data/lib/rails/generators/interactor/test_unit_generator.rb +3 -10
- data/lib/rails/generators/{active_interactor/templates/initializer.erb → templates/active_interactor.erb} +3 -11
- data/lib/rails/generators/{active_interactor/templates → templates}/application_context.rb +0 -0
- data/lib/rails/generators/{active_interactor/templates → templates}/application_interactor.rb +0 -0
- data/lib/rails/generators/templates/application_organizer.rb +4 -0
- data/lib/rails/generators/{interactor/templates → templates}/context.erb +0 -0
- data/lib/rails/generators/{interactor/context/templates/rspec.erb → templates/context_spec.erb} +0 -0
- data/lib/rails/generators/{interactor/context/templates/test_unit.erb → templates/context_test_unit.erb} +0 -0
- data/lib/rails/generators/{interactor/templates → templates}/interactor.erb +0 -0
- data/lib/rails/generators/{interactor/templates/rspec.erb → templates/interactor_spec.erb} +0 -0
- data/lib/rails/generators/{interactor/templates/test_unit.erb → templates/interactor_text_unit.erb} +0 -0
- data/lib/rails/generators/{interactor/templates → templates}/organizer.erb +0 -0
- data/spec/active_interactor/base_spec.rb +3 -3
- data/spec/active_interactor/interactor/{perform_options_spec.rb → perform/options_spec.rb} +1 -1
- data/spec/active_interactor/interactor/worker_spec.rb +14 -15
- data/spec/active_interactor/{organizer_spec.rb → organizer/base_spec.rb} +27 -17
- data/spec/integration/a_basic_interactor_spec.rb +106 -0
- data/spec/integration/a_basic_organizer_spec.rb +97 -0
- data/spec/integration/a_failing_interactor_spec.rb +42 -0
- data/spec/integration/active_record_integration_spec.rb +9 -9
- data/spec/integration/an_interactor_with_after_context_validation_callbacks_spec.rb +69 -0
- data/spec/integration/an_interactor_with_after_perform_callbacks_spec.rb +30 -0
- data/spec/integration/an_interactor_with_after_rollback_callbacks_spec.rb +33 -0
- data/spec/integration/an_interactor_with_an_existing_context_class_spec.rb +49 -0
- data/spec/integration/an_interactor_with_around_perform_callbacks_spec.rb +35 -0
- data/spec/integration/an_interactor_with_around_rollback_callbacks_spec.rb +39 -0
- data/spec/integration/an_interactor_with_before_perform_callbacks_spec.rb +30 -0
- data/spec/integration/an_interactor_with_before_rollback_callbacks_spec.rb +33 -0
- data/spec/integration/an_interactor_with_validations_on_called_spec.rb +40 -0
- data/spec/integration/an_interactor_with_validations_on_calling_spec.rb +36 -0
- data/spec/integration/an_interactor_with_validations_spec.rb +93 -0
- data/spec/integration/an_organizer_performing_in_parallel_spec.rb +48 -0
- data/spec/integration/an_organizer_with_after_each_callbacks_spec.rb +34 -0
- data/spec/integration/an_organizer_with_around_each_callbacks_spec.rb +39 -0
- data/spec/integration/an_organizer_with_before_each_callbacks_spec.rb +34 -0
- data/spec/integration/an_organizer_with_conditionally_organized_interactors_spec.rb +314 -0
- data/spec/spec_helper.rb +8 -12
- data/spec/support/coverage.rb +4 -0
- data/spec/support/coverage/reporters.rb +11 -0
- data/spec/support/coverage/reporters/codacy.rb +39 -0
- data/spec/support/coverage/reporters/simple_cov.rb +54 -0
- data/spec/support/coverage/runner.rb +66 -0
- data/spec/support/helpers/factories.rb +1 -1
- data/spec/support/shared_examples/a_class_with_interactor_callback_methods_example.rb +8 -8
- data/spec/support/shared_examples/a_class_with_interactor_context_methods_example.rb +5 -5
- data/spec/support/shared_examples/a_class_with_interactor_methods_example.rb +2 -2
- data/spec/support/shared_examples/a_class_with_organizer_callback_methods_example.rb +3 -3
- metadata +83 -40
- data/lib/active_interactor/interactor.rb +0 -84
- data/lib/active_interactor/interactor/perform_options.rb +0 -29
- data/lib/active_interactor/organizer.rb +0 -269
- data/lib/active_interactor/rails/config.rb +0 -45
- data/lib/active_interactor/rails/models.rb +0 -58
- data/lib/rails/generators/active_interactor/templates/application_organizer.rb +0 -4
- data/spec/active_interactor/rails/config_spec.rb +0 -29
- data/spec/active_interactor/rails_spec.rb +0 -24
- data/spec/integration/basic_callback_integration_spec.rb +0 -355
- data/spec/integration/basic_context_integration_spec.rb +0 -73
- data/spec/integration/basic_integration_spec.rb +0 -570
- data/spec/integration/basic_validations_integration_spec.rb +0 -204
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df118b852db85867fab87a6582604a59c89fd0a194843e569c663cb09b969abc
|
4
|
+
data.tar.gz: 281f4e6672238ccfac070bd0c17f6831f76b455c26444a49945388b85738153c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19822562f299f87b863686d0449e4c66b31096696f9f73c47e2c226a62413cdd8a379150a9e5d35a7d3f5e35433300b787965111a0bf0f9a0e29bcf4628afa04
|
7
|
+
data.tar.gz: dee8cdf2ad05de200a09549fd3ad4f2c904e34da01374230d69a6a040d2ea9c0542e76cae81a80b0bbd259f81c413784934e21e8069dbfda1b16a1acc1247056
|
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -7,197 +7,123 @@ and this project adheres to [Semantic Versioning].
|
|
7
7
|
|
8
8
|
## [Unreleased]
|
9
9
|
|
10
|
-
## [v1.0.0
|
10
|
+
## [v1.0.0] - 2020-01-26
|
11
11
|
|
12
12
|
### Added
|
13
13
|
|
14
|
-
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
- [#127] #126 `act_as_context` not working
|
19
|
-
|
20
|
-
### Removed
|
21
|
-
|
22
|
-
- [#127] `ActiveInteractor::Rails::ActiveRecord`
|
23
|
-
|
24
|
-
## [v1.0.0-beta.6] - 2020-01-21
|
25
|
-
|
26
|
-
### Added
|
27
|
-
|
28
|
-
- [#124] `ActiveInteractor::Context::Status`
|
29
|
-
- [#124] `ActiveInteractor::Rails::ActiveRecord`
|
30
|
-
|
31
|
-
### Changed
|
32
|
-
|
33
|
-
- [#124] Abstracted status methods from `ActiveInteractor::Context::Base`
|
34
|
-
into `ActiveInteractor::Context::Status`
|
35
|
-
|
36
|
-
## [v1.0.0-beta.5] - 2020-01-21
|
37
|
-
|
38
|
-
### Added
|
39
|
-
|
40
|
-
- [#122] `ActiveInteractor::Rails::Railtie`
|
41
|
-
- [#122] `ActiveInteractor::Rails.config`
|
42
|
-
- [#122] `ActiveInteractor::Rails.configure`
|
43
|
-
|
44
|
-
### Removed
|
45
|
-
|
46
|
-
- [#122] `ActiveInteractor::Railtie` use `ActiveInteractor::Rails::Railtie` instead
|
47
|
-
- [#122] `ActiveInteractor::Config#rails` use `ActiveInteractor::Rails.configure` instead
|
48
|
-
|
49
|
-
## [v1.0.0-beta.4] - 2020-01-14
|
50
|
-
|
51
|
-
### Added
|
52
|
-
|
53
|
-
- [#114] `ActiveInteractor::Organizer::InteractorInterface`
|
54
|
-
- [#114] `ActiveInteractor::Organizer::InteractorInterfaceCollection`
|
55
|
-
- [#115] `ActiveInteractor::Interactor#options`
|
56
|
-
- [#115] `ActiveInteractor::Interactor#with_options`
|
57
|
-
- [#115] `ActiveInteractor::Interactor::PerformOptions#skip_each_perform_callbacks`
|
58
|
-
|
59
|
-
### Changed
|
60
|
-
|
61
|
-
- [#115] `ActiveInteractor::Interactor::Worker#execute_perform` and `#execute_perform!` no longer accept arguments,
|
62
|
-
use `ActiveInteractor::Interactor#with_options` instead.
|
63
|
-
- [#115] `ActiveInteractor::Organizer` can now skip `each_perform` callbacks with
|
64
|
-
the option `skip_each_perform_callbacks`
|
65
|
-
|
66
|
-
### Removed
|
67
|
-
|
68
|
-
- [#115] `ActiveInteractor::Interactor#execute_rollback`
|
69
|
-
- [#115] `ActiveInteractor::Interactor::Worker#run_callbacks`
|
70
|
-
|
71
|
-
## [v1.0.0-beta.3] - 2020-01-12
|
72
|
-
|
73
|
-
### Added
|
74
|
-
|
75
|
-
- [#109] `ActiveInteractor::Organizer.parallel`
|
76
|
-
- [#109] `ActiveInteractor::Organizer.perform_in_parallel`
|
77
|
-
- [#109] `ActiveInteractor::Context::Base#merge`
|
78
|
-
- [#110] `ActiveInteractor::Interactor::PerformOptions`
|
79
|
-
|
80
|
-
### Changed
|
81
|
-
|
82
|
-
- [#110] `ActiveInteractor::Interactor.perform` now takes options
|
83
|
-
|
84
|
-
## [v1.0.0-beta.2] - 2020-01-07
|
85
|
-
|
86
|
-
### Added
|
87
|
-
|
88
|
-
- [#102] `ActiveInteractor::Config`
|
89
|
-
- [#102] `ActiveInteractor.config`
|
90
|
-
- [#103] `ActiveInteractor::Rails`
|
91
|
-
- [#103] `ActiveInteractor::Rails::Config`
|
92
|
-
- [#102] `ActiveInteractor::Railtie`
|
93
|
-
- [#105] interactor, organizer, and context generators now accept context_attributes
|
94
|
-
as arguments.
|
95
|
-
|
96
|
-
### Changed
|
97
|
-
|
98
|
-
- [#102] `ActiveInteractor.logger` is now part of `ActiveInteractor.config`
|
99
|
-
- [#104] Interactor generators will no longer generate separate context classes for
|
100
|
-
interactors if `ActiveInteractor.config.rails.generate_context_classes` is set to `false`
|
101
|
-
|
102
|
-
### Fixed
|
103
|
-
|
104
|
-
- [#103] various generator fixes
|
105
|
-
|
106
|
-
### Removed
|
107
|
-
|
108
|
-
- [#102] `ActiveInteractor.logger=` use `ActiveInteractor.config.logger=` instead
|
109
|
-
|
110
|
-
## [v1.0.0-beta.1] - 2020-01-06
|
111
|
-
|
112
|
-
### Added
|
113
|
-
|
114
|
-
- `ActiveInteractor.logger=`
|
115
|
-
- `ActiveInteractor::Base#dup`
|
14
|
+
- `ActiveInteractor::Config`
|
15
|
+
- `ActiveInteractor::Configurable`
|
16
|
+
- `ActiveInteractor::Context::Attributes#merge!`
|
17
|
+
- `ActiveInteractor::Context::Base#merge`
|
116
18
|
- `ActiveInteractor::Context::Loader`
|
19
|
+
- `ActiveInteractor::Context::Status`
|
117
20
|
- `ActiveInteractor::Error::InvalidContextClass`
|
21
|
+
- `ActiveInteractor::Models`
|
22
|
+
- `ActiveInteractor::Organizer::Callbacks`
|
23
|
+
- `ActiveInteractor::Organizer::InteractorInterface`
|
24
|
+
- `ActiveInteractor::Organizer::InteractorInterfaceCollection`
|
25
|
+
- `ActiveInteractor::Organizer::Organize`
|
26
|
+
- `ActiveInteractor::Organizer::Perform`
|
27
|
+
- `ActiveInteractor::Interactor::Context.contextualize_with`
|
118
28
|
- `ActiveInteractor::Interactor::Context#context_fail!`
|
119
29
|
- `ActiveInteractor::Interactor::Context#context_rollback!`
|
120
|
-
- `ActiveInteractor::Interactor::Context.contextualize_with`
|
121
30
|
- `ActiveInteractor::Interactor::Context#finalize_context!`
|
31
|
+
- `ActiveInteractor::Interactor::Perform`
|
32
|
+
- `ActiveInteractor::Interactor::Perform::Options`
|
33
|
+
- `ActiveInteractor::Rails`
|
34
|
+
- `ActiveInteractor::Rails::Railtie`
|
122
35
|
|
123
36
|
### Changed
|
124
37
|
|
38
|
+
- `ActiveInteractor::Base` now calls an `ActiveSupport.on_load` hook with `:active_interactor` and
|
39
|
+
`ActiveInteractor::Base`
|
125
40
|
- `ActiveInteractor::Context::Attributes.attributes` now excepts arguments for attributes
|
126
|
-
- `ActiveInteractor::
|
41
|
+
- `ActiveInteractor::Interactor.perform` now takes options
|
127
42
|
- `ActiveInteractor::Interactor::Context.context_class` will now first attempt to find an
|
128
43
|
existing context class, and only create a new context class if a context is not found.
|
129
|
-
- `ActiveInteractor::Organizer
|
44
|
+
- Moved `ActiveInteractor::Organizer` to `ActiveInteractor::Organizer::Base`
|
45
|
+
- interactor, organizer, and context generators now accept `context_attributes`
|
46
|
+
as arguments.
|
47
|
+
|
48
|
+
### Fixed
|
49
|
+
|
50
|
+
- various rails generator fixes
|
130
51
|
|
131
52
|
### Removed
|
132
53
|
|
133
|
-
- `ActiveInteractor::Configuration`
|
134
|
-
- `ActiveInteractor::Context::Attributes.attributes=`
|
54
|
+
- `ActiveInteractor::Configuration` use `ActiveInteractor::Config`
|
55
|
+
- `ActiveInteractor::Context::Attributes.attributes=` use `ActiveInteractor::Context#attributes`
|
135
56
|
- `ActiveInteractor::Context::Attributes.attribute_aliases`
|
57
|
+
- `ActiveInteractor::Context::Attributes.alias_attributes`
|
136
58
|
- `ActiveInteractor::Context::Attributes#clean!`
|
137
59
|
- `ActiveInteractor::Context::Attributes#keys`
|
138
60
|
- `ActiveInteractor::Interactor#fail_on_invalid_context?`
|
61
|
+
- `ActiveInteractor::Interactor#execute_rollback`
|
139
62
|
- `ActiveInteractor::Interactor#should_clean_context?`
|
63
|
+
- `ActiveInteractor::Interactor#skip_clean_context!`
|
64
|
+
- `ActiveInteractor::Interactor::Callbacks.allow_context_to_be_invalid`
|
140
65
|
- `ActiveInteractor::Interactor::Callbacks.clean_context_on_completion`
|
141
66
|
- `ActiveInteractor::Interactor::Context.context_attribute_aliases`
|
142
67
|
- `ActiveInteractor::Interactor::Execution`
|
68
|
+
- `ActiveInteractor::Interactor::Worker#run_callbacks`
|
143
69
|
|
144
70
|
## [v0.1.7] - 2019-09-10
|
145
71
|
|
146
72
|
### Fixed
|
147
73
|
|
148
|
-
-
|
74
|
+
- Ensure `Organizer` accurately reports context success
|
149
75
|
|
150
76
|
## [v0.1.6] - 2019-07-24
|
151
77
|
|
152
78
|
### Changed
|
153
79
|
|
154
|
-
-
|
80
|
+
- Lowered method complexity and enforced single responsibility
|
155
81
|
|
156
82
|
### Security
|
157
83
|
|
158
|
-
-
|
159
|
-
-
|
84
|
+
- Update simplecov: 0.16.1 → 0.17.0 (major)
|
85
|
+
- Update rake: 12.3.2 → 12.3.3 (patch)
|
160
86
|
|
161
87
|
## [v0.1.5] - 2019-06-30
|
162
88
|
|
163
89
|
### Added
|
164
90
|
|
165
|
-
-
|
91
|
+
- `ActiveInteractor::Error` module
|
166
92
|
|
167
93
|
### Deprecated
|
168
94
|
|
169
|
-
-
|
95
|
+
- `ActiveInteractor::Context::Failure` in favor of `ActiveInteractor::Error::ContextFailure`
|
170
96
|
|
171
97
|
### Security
|
172
98
|
|
173
|
-
-
|
174
|
-
-
|
175
|
-
-
|
99
|
+
- Update rubocop: 0.67.2 → 0.72.0 (major)
|
100
|
+
- Various dependency updates
|
101
|
+
- Update yard: 0.9.19 → 0.9.20 (minor)
|
176
102
|
|
177
103
|
## [v0.1.4] - 2019-04-12
|
178
104
|
|
179
105
|
### Added
|
180
106
|
|
181
|
-
-
|
107
|
+
- The ability to alias attributes on interactor contexts.
|
182
108
|
|
183
109
|
## [v0.1.3] - 2019-04-01
|
184
110
|
|
185
111
|
### Added
|
186
112
|
|
187
|
-
-
|
113
|
+
- Implement `each_perform` callbacks on organizers
|
188
114
|
|
189
115
|
## [v0.1.2] - 2019-04-01
|
190
116
|
|
191
117
|
### Added
|
192
118
|
|
193
|
-
-
|
119
|
+
- Allow the directory interactors are generated in to be configurable
|
194
120
|
|
195
121
|
## [v0.1.1] - 2019-03-30
|
196
122
|
|
197
123
|
### Fixed
|
198
124
|
|
199
|
-
-
|
200
|
-
-
|
125
|
+
- `NameError` (uninitialized constant `ActiveInteractor::Organizer`)
|
126
|
+
- `NoMethodError` (undefined method `merge` for `ActiveInteractor::Context::Base`)
|
201
127
|
|
202
128
|
## v0.1.0 - 2019-03-30
|
203
129
|
|
@@ -208,14 +134,8 @@ and this project adheres to [Semantic Versioning].
|
|
208
134
|
|
209
135
|
<!-- versions -->
|
210
136
|
|
211
|
-
[Unreleased]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0
|
212
|
-
[v1.0.0
|
213
|
-
[v1.0.0-beta.6]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.5...v1.0.0-beta.6
|
214
|
-
[v1.0.0-beta.5]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.4...v1.0.0-beta.5
|
215
|
-
[v1.0.0-beta.4]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.3...v1.0.0-beta.4
|
216
|
-
[v1.0.0-beta.3]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.2...v1.0.0-beta.3
|
217
|
-
[v1.0.0-beta.2]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.1...v1.0.0-beta.2
|
218
|
-
[v1.0.0-beta.1]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.7...v1.0.0-beta.1
|
137
|
+
[Unreleased]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0...HEAD
|
138
|
+
[v1.0.0]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.7...v1.0.0
|
219
139
|
[v0.1.7]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.6...v0.1.7
|
220
140
|
[v0.1.6]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.5...v0.1.6
|
221
141
|
[v0.1.5]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.4...v0.1.5
|
@@ -223,31 +143,3 @@ and this project adheres to [Semantic Versioning].
|
|
223
143
|
[v0.1.3]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.2...v0.1.3
|
224
144
|
[v0.1.2]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.1...v0.1.2
|
225
145
|
[v0.1.1]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.0...v0.1.1
|
226
|
-
|
227
|
-
<!-- pull requests and issues -->
|
228
|
-
|
229
|
-
[#15]: https://github.com/aaronmallen/activeinteractor/pull/15
|
230
|
-
[#16]: https://github.com/aaronmallen/activeinteractor/pull/16
|
231
|
-
[#22]: https://github.com/aaronmallen/activeinteractor/pull/22
|
232
|
-
[#25]: https://github.com/aaronmallen/activeinteractor/pull/25
|
233
|
-
[#28]: https://github.com/aaronmallen/activeinteractor/pull/28
|
234
|
-
[#33]: https://github.com/aaronmallen/activeinteractor/pull/33
|
235
|
-
[#34]: https://github.com/aaronmallen/activeinteractor/pull/34
|
236
|
-
[#37]: https://github.com/aaronmallen/activeinteractor/pull/37
|
237
|
-
[#38]: https://github.com/aaronmallen/activeinteractor/pull/38
|
238
|
-
[#39]: https://github.com/aaronmallen/activeinteractor/pull/39
|
239
|
-
[#45]: https://github.com/aaronmallen/activeinteractor/pull/45
|
240
|
-
[#48]: https://github.com/aaronmallen/activeinteractor/pull/48
|
241
|
-
[#51]: https://github.com/aaronmallen/activeinteractor/pull/51
|
242
|
-
[#61]: https://github.com/aaronmallen/activeinteractor/pull/61
|
243
|
-
[#102]: https://github.com/aaronmallen/activeinteractor/pull/102
|
244
|
-
[#103]: https://github.com/aaronmallen/activeinteractor/pull/103
|
245
|
-
[#104]: https://github.com/aaronmallen/activeinteractor/pull/104
|
246
|
-
[#105]: https://github.com/aaronmallen/activeinteractor/pull/105
|
247
|
-
[#109]: https://github.com/aaronmallen/activeinteractor/pull/109
|
248
|
-
[#110]: https://github.com/aaronmallen/activeinteractor/pull/110
|
249
|
-
[#114]: https://github.com/aaronmallen/activeinteractor/pull/114
|
250
|
-
[#115]: https://github.com/aaronmallen/activeinteractor/pull/115
|
251
|
-
[#122]: https://github.com/aaronmallen/activeinteractor/pull/122
|
252
|
-
[#124]: https://github.com/aaronmallen/activeinteractor/pull/124
|
253
|
-
[#127]: https://github.com/aaronmallen/activeinteractor/pull/127
|
data/README.md
CHANGED
@@ -8,50 +8,16 @@
|
|
8
8
|
[![Code Quality](https://api.codacy.com/project/badge/Grade/be92c4ecf12347da82d266f6a4368b6e)](https://www.codacy.com/manual/aaronmallen/activeinteractor?utm_source=github.com&utm_medium=referral&utm_content=aaronmallen/activeinteractor&utm_campaign=Badge_Grade)
|
9
9
|
[![Test Coverage](https://api.codacy.com/project/badge/Coverage/be92c4ecf12347da82d266f6a4368b6e)](https://www.codacy.com/manual/aaronmallen/activeinteractor?utm_source=github.com&utm_medium=referral&utm_content=aaronmallen/activeinteractor&utm_campaign=Badge_Coverage)
|
10
10
|
|
11
|
-
Ruby
|
12
|
-
|
13
|
-
|
14
|
-
see [v0.1.7](https://github.com/aaronmallen/activeinteractor/tree/0-1-stable)**
|
15
|
-
|
16
|
-
<!-- TOC -->
|
17
|
-
|
18
|
-
* [Getting Started](#getting-started)
|
19
|
-
* [What is an Interactor](#what-is-an-interactor)
|
20
|
-
* [Usage](#usage)
|
21
|
-
* [Context](#context)
|
22
|
-
* [Adding to the Context](#adding-to-the-context)
|
23
|
-
* [Failing the Context](#failing-the-context)
|
24
|
-
* [Dealing with Failure](#dealing-with-failure)
|
25
|
-
* [Context Attributes](#context-attributes)
|
26
|
-
* [Validating the Context](#validating-the-context)
|
27
|
-
* [Using Interactors](#using-interactors)
|
28
|
-
* [Kinds of Interactors](#kinds-of-interactors)
|
29
|
-
* [Interactors](#interactors)
|
30
|
-
* [Organizers](#organizers)
|
31
|
-
* [Organizing Interactors Conditionally](#organizing-interactors-conditionally)
|
32
|
-
* [Running Interactors In Parallel](#running-interactors-in-parallel)
|
33
|
-
* [Rollback](#rollback)
|
34
|
-
* [Callbacks](#callbacks)
|
35
|
-
* [Validation Callbacks](#validation-callbacks)
|
36
|
-
* [Perform Callbacks](#perform-callbacks)
|
37
|
-
* [Rollback Callbacks](#rollback-callbacks)
|
38
|
-
* [Organizer Callbacks](#organizer-callbacks)
|
39
|
-
* [Working With Rails](#working-with-rails)
|
40
|
-
* [Generators](#generators)
|
41
|
-
* [ActiveRecord Helper Methods](#activerecord-helper-methods)
|
42
|
-
* [Development](#development)
|
43
|
-
* [Contributing](#contributing)
|
44
|
-
* [Acknowledgements](#acknowledgements)
|
45
|
-
* [License](#license)
|
46
|
-
|
47
|
-
<!-- TOC -->
|
11
|
+
An implementation of the [command pattern] for Ruby with [ActiveModel::Validations] based on the
|
12
|
+
[interactor][collective_idea_interactors] gem. Rich support for attributes, callbacks, and validations,
|
13
|
+
and thread safe performance methods.
|
48
14
|
|
49
15
|
## Getting Started
|
50
16
|
|
51
17
|
Add this line to your application's Gemfile:
|
52
18
|
|
53
19
|
```ruby
|
54
|
-
gem 'activeinteractor'
|
20
|
+
gem 'activeinteractor'
|
55
21
|
```
|
56
22
|
|
57
23
|
And then execute:
|
@@ -63,824 +29,14 @@ bundle
|
|
63
29
|
Or install it yourself as:
|
64
30
|
|
65
31
|
```bash
|
66
|
-
gem install activeinteractor
|
32
|
+
gem install activeinteractor
|
67
33
|
```
|
68
34
|
|
69
|
-
## What is an Interactor
|
70
|
-
|
71
|
-
An interactor is a simple, single-purpose service object.
|
72
|
-
|
73
|
-
Interactors can be used to reduce the responsibility of your controllers,
|
74
|
-
workers, and models and encapsulate your application's [business logic][business_logic_wikipedia].
|
75
|
-
Each interactor represents one thing that your application does.
|
76
|
-
|
77
35
|
## Usage
|
78
36
|
|
79
|
-
|
80
|
-
|
81
|
-
Each interactor will have it's own immutable context and context class. All context classes should
|
82
|
-
inherit from `ActiveInteractor::Context::Base`. By default an interactor will attempt to find an existing
|
83
|
-
class following the naming conventions: `MyInteractor::Context` or `MyInteractorContext`. If no class
|
84
|
-
is found a context class will be created using the naming convention `MyInteractor::Context` for example:
|
85
|
-
|
86
|
-
```ruby
|
87
|
-
class MyInteractor < ActiveInteractor::Base; end
|
88
|
-
class MyInteractor::Context < ActiveInteractor::Context::Base; end
|
89
|
-
|
90
|
-
MyInteractor.context_class #=> MyInteractor::Context
|
91
|
-
```
|
92
|
-
|
93
|
-
```ruby
|
94
|
-
class MyInteractorContext < ActiveInteractor::Context::Base; end
|
95
|
-
class MyInteractor < ActiveInteractor::Base; end
|
96
|
-
|
97
|
-
MyInteractor.context_class #=> MyInteractorContext
|
98
|
-
```
|
99
|
-
|
100
|
-
```ruby
|
101
|
-
class MyInteractor < ActiveInteractor::Base; end
|
102
|
-
|
103
|
-
MyInteractor.context_class #=> MyInteractor::Context
|
104
|
-
```
|
105
|
-
|
106
|
-
Additionally you can manually specify a context for an interactor with the `contextualize_with`
|
107
|
-
method.
|
108
|
-
|
109
|
-
```ruby
|
110
|
-
class MyGenericContext < ActiveInteractor::Context::Base; end
|
111
|
-
|
112
|
-
class MyInteractor
|
113
|
-
contextualize_with :my_generic_context
|
114
|
-
end
|
115
|
-
|
116
|
-
MyInteractor.context_class #=> MyGenericContext
|
117
|
-
```
|
118
|
-
|
119
|
-
An interactor's context contains everything the interactor needs to do its work. When an interactor does its single purpose,
|
120
|
-
it affects its given context.
|
121
|
-
|
122
|
-
#### Adding to the Context
|
123
|
-
|
124
|
-
All instances of context inherit from `OpenStruct`. As an interactor runs it can add information to
|
125
|
-
it's context.
|
126
|
-
|
127
|
-
```ruby
|
128
|
-
class MyInteractor
|
129
|
-
def perform
|
130
|
-
context.user = User.create(...)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
```
|
134
|
-
|
135
|
-
#### Failing the Context
|
136
|
-
|
137
|
-
When something goes wrong in your interactor, you can flag the context as failed.
|
138
|
-
|
139
|
-
```ruby
|
140
|
-
context.fail!
|
141
|
-
```
|
142
|
-
|
143
|
-
When given an argument of an instance of `ActiveModel::Errors`, the `#fail!` method can also update the context.
|
144
|
-
The following are equivalent:
|
145
|
-
|
146
|
-
```ruby
|
147
|
-
context.errors.merge!(user.errors)
|
148
|
-
context.
|
149
|
-
```
|
150
|
-
|
151
|
-
```ruby
|
152
|
-
context.fail!(user.errors)
|
153
|
-
```
|
154
|
-
|
155
|
-
You can ask a context if it's a failure:
|
156
|
-
|
157
|
-
```ruby
|
158
|
-
class MyInteractor
|
159
|
-
def perform
|
160
|
-
context.fail!
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
result = MyInteractor.perform
|
165
|
-
result.failure? #=> true
|
166
|
-
```
|
167
|
-
|
168
|
-
or if it's a success:
|
169
|
-
|
170
|
-
```ruby
|
171
|
-
class MyInteractor
|
172
|
-
def perform
|
173
|
-
context.user = User.create(...)
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
result = MyInteractor.perform
|
178
|
-
result.success? #=> true
|
179
|
-
```
|
180
|
-
|
181
|
-
#### Dealing with Failure
|
182
|
-
|
183
|
-
`context.fail!` always throws an exception of type `ActiveInteractor::Error::ContextFailure`.
|
184
|
-
|
185
|
-
Normally, however, these exceptions are not seen. In the recommended usage, the consuming object invokes the interactor
|
186
|
-
using the class method `perform`, then checks the `success?` method of the context.
|
187
|
-
|
188
|
-
This works because the `perform` class method swallows exceptions. When unit testing an interactor, if calling custom business
|
189
|
-
logic methods directly and bypassing `perform`, be aware that `fail!` will generate such exceptions.
|
190
|
-
|
191
|
-
See [Using Interactors](#using-interactors), below, for the recommended usage of `perform` and `success?`.
|
192
|
-
|
193
|
-
#### Context Attributes
|
194
|
-
|
195
|
-
Each context instance has basic attribute assignment methods. Assigning attributes to a context is a simple way to
|
196
|
-
explicitly defined what properties a context should have after an interactor has done it's work.
|
197
|
-
|
198
|
-
You can see what attributes are defined on a given context with the `#attributes` method:
|
199
|
-
|
200
|
-
```ruby
|
201
|
-
class MyInteractorContext < ActiveInteractor::Context::Base
|
202
|
-
attributes :first_name, :last_name, :email, :user
|
203
|
-
end
|
204
|
-
|
205
|
-
class MyInteractor < ActiveInteractor::Base; end
|
206
|
-
|
207
|
-
result = MyInteractor.perform(
|
208
|
-
first_name: 'Aaron',
|
209
|
-
last_name: 'Allen',
|
210
|
-
email: 'hello@aaronmallen.me',
|
211
|
-
occupation: 'Software Dude'
|
212
|
-
)
|
213
|
-
#=> <#MyInteractor::Context first_name='Aaron' last_name='Allen' email='hello@aaronmallen.me' occupation='Software Dude'>
|
214
|
-
|
215
|
-
result.attributes #=> { first_name: 'Aaron', last_name: 'Allen', email: 'hello@aaronmallen.me' }
|
216
|
-
result.occupation #=> 'Software Dude'
|
217
|
-
```
|
218
|
-
|
219
|
-
#### Validating the Context
|
220
|
-
|
221
|
-
All context instances include [ActiveModel::Validations]; additionally ActiveInteractor delegates all the validation methods
|
222
|
-
provided by [ActiveModel::Validations] onto an interactor's context class from the interactor itself. All of the methods
|
223
|
-
found in [ActiveModel::Validations] can be invoked directly on your interactor with the prefix `context_`.
|
224
|
-
However this can be confusing and it is recommended to make all validation calls on a context class directly.
|
225
|
-
|
226
|
-
ActiveInteractor provides two validation callback steps:
|
37
|
+
Be sure to read the [wiki] for detailed information on how to use ActiveInteractor.
|
227
38
|
|
228
|
-
|
229
|
-
* `:called` used after `#perform` is invoked on an interactor
|
230
|
-
|
231
|
-
A basic implementation might look like this:
|
232
|
-
|
233
|
-
```ruby
|
234
|
-
class MyInteractorContext < ActiveInteractor::Context::Base
|
235
|
-
attributes :first_name, :last_name, :email, :user
|
236
|
-
# only validates presence before perform is invoked
|
237
|
-
validates :first_name, presence: true, on: :calling
|
238
|
-
# validates before and after perform is invoked
|
239
|
-
validates :email, presence: true,
|
240
|
-
format: { with: URI::MailTo::EMAIL_REGEXP }
|
241
|
-
# validates after perform is invoked
|
242
|
-
validates :user, presence: true, on: :called
|
243
|
-
validate :user_is_a_user, on: :called
|
244
|
-
|
245
|
-
private
|
246
|
-
|
247
|
-
def user_is_a_user
|
248
|
-
return if user.is_a?(User)
|
249
|
-
|
250
|
-
errors.add(:user, :invalid)
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
class MyInteractor < ActiveInteractor::Base
|
255
|
-
def perform
|
256
|
-
context.user = User.create_with(
|
257
|
-
first_name: context.first_name,
|
258
|
-
last_name: context.last_name
|
259
|
-
).find_or_create_by(email: context.email)
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
result = MyInteractor.perform(last_name: 'Allen')
|
264
|
-
#=> <#MyInteractor::Context last_name='Allen>
|
265
|
-
result.failure? #=> true
|
266
|
-
result.valid? #=> false
|
267
|
-
result.errors[:first_name] #=> ['can not be blank']
|
268
|
-
|
269
|
-
result = MyInterator.perform(first_name: 'Aaron', email: 'hello@aaronmallen.me')
|
270
|
-
#=> <#MyInteractor::Context first_name='Aaron' email='hello@aaronmallen.me' user=<#User ...>>
|
271
|
-
result.success? #=> true
|
272
|
-
result.valid? #=> true
|
273
|
-
result.errors.empty? #=> true
|
274
|
-
```
|
275
|
-
|
276
|
-
### Using Interactors
|
277
|
-
|
278
|
-
Most of the time, your application will use its interactors from its controllers. The following controller:
|
279
|
-
|
280
|
-
```ruby
|
281
|
-
class SessionsController < ApplicationController
|
282
|
-
def create
|
283
|
-
if user = User.authenticate(session_params[:email], session_params[:password])
|
284
|
-
session[:user_token] = user.secret_token
|
285
|
-
redirect_to user
|
286
|
-
else
|
287
|
-
flash.now[:message] = "Please try again."
|
288
|
-
render :new
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
private
|
293
|
-
|
294
|
-
def session_params
|
295
|
-
params.require(:session).permit(:email, :password)
|
296
|
-
end
|
297
|
-
end
|
298
|
-
```
|
299
|
-
|
300
|
-
can be refactored to:
|
301
|
-
|
302
|
-
```ruby
|
303
|
-
class SessionsController < ApplicationController
|
304
|
-
def create
|
305
|
-
result = AuthenticateUser.perform(session_params)
|
306
|
-
|
307
|
-
if result.success?
|
308
|
-
session[:user_token] = result.token
|
309
|
-
redirect_to result.user
|
310
|
-
else
|
311
|
-
flash.now[:message] = t(result.errors.full_messages)
|
312
|
-
render :new
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
private
|
317
|
-
|
318
|
-
def session_params
|
319
|
-
params.require(:session).permit(:email, :password)
|
320
|
-
end
|
321
|
-
end
|
322
|
-
```
|
323
|
-
|
324
|
-
given the basic interactor and context:
|
325
|
-
|
326
|
-
```ruby
|
327
|
-
class AuthenticateUserContext < ActiveInteractor::Context::Base
|
328
|
-
attributes :email, :password, :user, :token
|
329
|
-
validates :email, presence: true,
|
330
|
-
format: { with: URI::MailTo::EMAIL_REGEXP }
|
331
|
-
validates :password, presence: true
|
332
|
-
validates :user, presence: true, on: :called
|
333
|
-
end
|
334
|
-
|
335
|
-
class AuthenticateUser < ActiveInteractor::Base
|
336
|
-
def perform
|
337
|
-
context.user = User.authenticate(
|
338
|
-
context.email,
|
339
|
-
context.password
|
340
|
-
)
|
341
|
-
context.token = context.user.secret_token
|
342
|
-
end
|
343
|
-
end
|
344
|
-
```
|
345
|
-
|
346
|
-
The `.perform` class method is the proper way to invoke an interactor. The hash argument is converted to the interactor instance's
|
347
|
-
context. The `#perform` instance method is invoked along with any callbacks and validations that the interactor might define.
|
348
|
-
Finally, the context (along with any changes made to it) is returned.
|
349
|
-
|
350
|
-
#### Kinds of Interactors
|
351
|
-
|
352
|
-
There are two kinds of interactors built into ActiveInteractor: basic interactors and organizers.
|
353
|
-
|
354
|
-
##### Interactors
|
355
|
-
|
356
|
-
A basic interactor is a class that inherits from `ActiveInteractor::Base` and defines `perform`.
|
357
|
-
|
358
|
-
```ruby
|
359
|
-
class AuthenticateUser < ActiveInteractor::Base
|
360
|
-
def perform
|
361
|
-
user = User.authenticate(context.email, context.password)
|
362
|
-
if user
|
363
|
-
context.user = user
|
364
|
-
context.token = user.secret_token
|
365
|
-
else
|
366
|
-
context.fail!
|
367
|
-
end
|
368
|
-
end
|
369
|
-
end
|
370
|
-
```
|
371
|
-
|
372
|
-
Basic interactors are the building blocks. They are your application's single-purpose units of work.
|
373
|
-
|
374
|
-
##### Organizers
|
375
|
-
|
376
|
-
An organizer is an important variation on the basic interactor. Its single purpose is to run other interactors.
|
377
|
-
|
378
|
-
```ruby
|
379
|
-
class CreateOrder < ActiveInteractor::Base
|
380
|
-
def perform
|
381
|
-
...
|
382
|
-
end
|
383
|
-
end
|
384
|
-
|
385
|
-
class ChargeCard < ActiveInteractor::Base
|
386
|
-
def perform
|
387
|
-
...
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|
391
|
-
class SendThankYou < ActiveInteractor::Base
|
392
|
-
def perform
|
393
|
-
...
|
394
|
-
end
|
395
|
-
end
|
396
|
-
|
397
|
-
class PlaceOrder < ActiveInteractor::Organizer
|
398
|
-
|
399
|
-
organize :create_order, :charge_card, :send_thank_you
|
400
|
-
end
|
401
|
-
```
|
402
|
-
|
403
|
-
In the controller, you can run the `PlaceOrder` organizer just like you would any other interactor:
|
404
|
-
|
405
|
-
```ruby
|
406
|
-
class OrdersController < ApplicationController
|
407
|
-
def create
|
408
|
-
result = PlaceOrder.perform(order_params: order_params)
|
409
|
-
|
410
|
-
if result.success?
|
411
|
-
redirect_to result.order
|
412
|
-
else
|
413
|
-
@order = result.order
|
414
|
-
render :new
|
415
|
-
end
|
416
|
-
end
|
417
|
-
|
418
|
-
private
|
419
|
-
|
420
|
-
def order_params
|
421
|
-
params.require(:order).permit!
|
422
|
-
end
|
423
|
-
end
|
424
|
-
```
|
425
|
-
|
426
|
-
The organizer passes its context to the interactors that it organizes, one at a time and in order. Each interactor may
|
427
|
-
change that context before it's passed along to the next interactor.
|
428
|
-
|
429
|
-
###### Organizing Interactors Conditionally
|
430
|
-
|
431
|
-
We can also add conditional statements to our organizer by passing a block to the `.organize` method:
|
432
|
-
|
433
|
-
```ruby
|
434
|
-
class PlaceOrder < ActiveInteractor::Organizer
|
435
|
-
organize do
|
436
|
-
add :create_order, if :user_registered?
|
437
|
-
add :charge_card, if: -> { context.order }
|
438
|
-
add :send_thank_you, if: -> { context.order }
|
439
|
-
end
|
440
|
-
|
441
|
-
private
|
442
|
-
|
443
|
-
def user_registered?
|
444
|
-
context.user&.registered?
|
445
|
-
end
|
446
|
-
end
|
447
|
-
```
|
448
|
-
|
449
|
-
###### Running Interactors In Parallel
|
450
|
-
|
451
|
-
Organizers can be told to run their interactors in parallel with the `.perform_in_parallel` class method. This
|
452
|
-
will run each interactor in parallel with one and other only passing the original context to each interactor.
|
453
|
-
This means each interactor must be able to perform without dependencies on prior interactor invokations.
|
454
|
-
|
455
|
-
```ruby
|
456
|
-
class CreateNewUser < ActiveInteractor::Base
|
457
|
-
def perform
|
458
|
-
context.user = User.create(
|
459
|
-
first_name: context.first_name,
|
460
|
-
last_name: context.last_name
|
461
|
-
)
|
462
|
-
end
|
463
|
-
end
|
464
|
-
|
465
|
-
class LogNewUserCreation < ActiveInteractor::Base
|
466
|
-
def perform
|
467
|
-
context.log = Log.create(
|
468
|
-
event: 'new user created',
|
469
|
-
first_name: context.first_name,
|
470
|
-
last_name: context.last_name
|
471
|
-
)
|
472
|
-
end
|
473
|
-
end
|
474
|
-
|
475
|
-
class CreateUser < ActiveInteractor::Organizer
|
476
|
-
perform_in_parallel
|
477
|
-
organize :create_new_user, :log_new_user_creation
|
478
|
-
end
|
479
|
-
|
480
|
-
CreateUser.perform(first_name: 'Aaron', last_name: 'Allen')
|
481
|
-
#=> <#CreateUser::Context first_name='Aaron' last_name='Allen' user=>#<User ...> log=<#Log ...>>
|
482
|
-
```
|
483
|
-
|
484
|
-
#### Rollback
|
485
|
-
|
486
|
-
If any one of the organized interactors fails its context, the organizer stops. If the `ChargeCard` interactor fails,
|
487
|
-
`SendThankYou` is never called.
|
488
|
-
|
489
|
-
In addition, any interactors that had already run are given the chance to undo themselves, in reverse order.
|
490
|
-
Simply define the rollback method on your interactors:
|
491
|
-
|
492
|
-
```ruby
|
493
|
-
class CreateOrder < ActiveInteractor::Base
|
494
|
-
def perform
|
495
|
-
order = Order.create(order_params)
|
496
|
-
|
497
|
-
if order.persisted?
|
498
|
-
context.order = order
|
499
|
-
else
|
500
|
-
context.fail!
|
501
|
-
end
|
502
|
-
end
|
503
|
-
|
504
|
-
def rollback
|
505
|
-
context.order.destroy
|
506
|
-
end
|
507
|
-
end
|
508
|
-
```
|
509
|
-
|
510
|
-
#### Callbacks
|
511
|
-
|
512
|
-
ActiveInteractor uses [ActiveModel::Callbacks] and [ActiveModel::Validations::Callbacks] on context validation, perform,
|
513
|
-
and rollback. Callbacks can be defined with a `block`, `Proc`, or `Symbol` method name and take the same conditional arguments
|
514
|
-
outlined in those two modules.
|
515
|
-
|
516
|
-
##### Validation Callbacks
|
517
|
-
|
518
|
-
We can do work before an interactor's context is validated with the `.before_context_validation` method:
|
519
|
-
|
520
|
-
```ruby
|
521
|
-
class MyInteractorContext < ActiveInteractor::Context::Base
|
522
|
-
attributes :first_name, :last_name, :email
|
523
|
-
validates :last_name, presence: true
|
524
|
-
end
|
525
|
-
|
526
|
-
class MyInteractor < ActiveInteractor::Base
|
527
|
-
before_context_validation { context.last_name ||= 'Unknown' }
|
528
|
-
end
|
529
|
-
|
530
|
-
result = MyInteractor.perform(first_name: 'Aaron', email: 'hello@aaronmallen.me')
|
531
|
-
result.valid? #=> true
|
532
|
-
result.last_name #=> 'Unknown'
|
533
|
-
```
|
534
|
-
|
535
|
-
We can do work after an interactor's context is validated with the `.after_context_validation` method:
|
536
|
-
|
537
|
-
```ruby
|
538
|
-
class MyInteractorContext < ActiveInteractor::Context::Base
|
539
|
-
attributes :first_name, :last_name, :email
|
540
|
-
validates :email, presence: true,
|
541
|
-
format: { with: URI::MailTo::EMAIL_REGEXP }
|
542
|
-
end
|
543
|
-
|
544
|
-
class MyInteractor < ActiveInteractor::Base
|
545
|
-
after_context_validation { context.email&.downcase! }
|
546
|
-
end
|
547
|
-
|
548
|
-
result = MyInteractor.perform(first_name: 'Aaron', last_name: 'Allen', email: 'HELLO@AARONMALLEN.ME')
|
549
|
-
result.valid? #=> true
|
550
|
-
result.email #=> 'hello@aaronmallen.me'
|
551
|
-
```
|
552
|
-
|
553
|
-
##### Perform Callbacks
|
554
|
-
|
555
|
-
We can do work before `#perform` is invoked with the `.before_perform` method:
|
556
|
-
|
557
|
-
```ruby
|
558
|
-
class MyInteractor < ActiveInteractor::Base
|
559
|
-
before_perform :print_start
|
560
|
-
|
561
|
-
def perform
|
562
|
-
puts 'Performing'
|
563
|
-
end
|
564
|
-
|
565
|
-
private
|
566
|
-
|
567
|
-
def print_start
|
568
|
-
puts 'Start'
|
569
|
-
end
|
570
|
-
end
|
571
|
-
|
572
|
-
MyInteractor.perform
|
573
|
-
"Start"
|
574
|
-
"Performing"
|
575
|
-
#=> <#MyInteractor::Context...>
|
576
|
-
```
|
577
|
-
|
578
|
-
We can do work around `#perform` invokation with the `.around_perform` method:
|
579
|
-
|
580
|
-
```ruby
|
581
|
-
class MyInteractor < ActiveInteractor::Base
|
582
|
-
around_perform :track_time
|
583
|
-
|
584
|
-
def perform
|
585
|
-
sleep(1)
|
586
|
-
end
|
587
|
-
|
588
|
-
private
|
589
|
-
|
590
|
-
def track_time
|
591
|
-
context.start_time = Time.now.utc
|
592
|
-
yield
|
593
|
-
context.end_time = Time.now.utc
|
594
|
-
end
|
595
|
-
end
|
596
|
-
|
597
|
-
result = MyInteractor.perform
|
598
|
-
result.start_time #=> 2019-01-01 00:00:00 UTC
|
599
|
-
result.end_time #=> 2019-01-01 00:00:01 UTC
|
600
|
-
```
|
601
|
-
|
602
|
-
We can do work after `#perform` is invoked with the `.after_perform` method:
|
603
|
-
|
604
|
-
```ruby
|
605
|
-
class MyInteractor < ActiveInteractor::Base
|
606
|
-
after_perform :print_done
|
607
|
-
|
608
|
-
def perform
|
609
|
-
puts 'Performing'
|
610
|
-
end
|
611
|
-
|
612
|
-
private
|
613
|
-
|
614
|
-
def print_done
|
615
|
-
puts 'Done'
|
616
|
-
end
|
617
|
-
end
|
618
|
-
|
619
|
-
MyInteractor.perform
|
620
|
-
"Performing"
|
621
|
-
"Done"
|
622
|
-
#=> <#MyInteractor::Context...>
|
623
|
-
```
|
624
|
-
|
625
|
-
##### Rollback Callbacks
|
626
|
-
|
627
|
-
We can do work before `#rollback` is invoked with the `.before_rollback` method:
|
628
|
-
|
629
|
-
```ruby
|
630
|
-
class MyInteractor < ActiveInteractor::Base
|
631
|
-
before_rollback :print_start
|
632
|
-
|
633
|
-
def perform
|
634
|
-
context.fail!
|
635
|
-
end
|
636
|
-
|
637
|
-
def rollback
|
638
|
-
puts 'Rolling Back'
|
639
|
-
end
|
640
|
-
|
641
|
-
private
|
642
|
-
|
643
|
-
def print_start
|
644
|
-
puts 'Start'
|
645
|
-
end
|
646
|
-
end
|
647
|
-
|
648
|
-
MyInteractor.perform
|
649
|
-
"Start"
|
650
|
-
"Rolling Back"
|
651
|
-
#=> <#MyInteractor::Context...>
|
652
|
-
```
|
653
|
-
|
654
|
-
We can do work around `#rollback` invokation with the `.around_rollback` method:
|
655
|
-
|
656
|
-
```ruby
|
657
|
-
class MyInteractor < ActiveInteractor::Base
|
658
|
-
around_rollback :track_time
|
659
|
-
|
660
|
-
def perform
|
661
|
-
context.fail!
|
662
|
-
end
|
663
|
-
|
664
|
-
def rollback
|
665
|
-
sleep(1)
|
666
|
-
end
|
667
|
-
|
668
|
-
private
|
669
|
-
|
670
|
-
def track_time
|
671
|
-
context.start_time = Time.now.utc
|
672
|
-
yield
|
673
|
-
context.end_time = Time.now.utc
|
674
|
-
end
|
675
|
-
end
|
676
|
-
|
677
|
-
result = MyInteractor.perform
|
678
|
-
result.start_time #=> 2019-01-01 00:00:00 UTC
|
679
|
-
result.end_time #=> 2019-01-01 00:00:01 UTC
|
680
|
-
```
|
681
|
-
|
682
|
-
We can do work after `#rollback` is invoked with the `.after_rollback` method:
|
683
|
-
|
684
|
-
```ruby
|
685
|
-
class MyInteractor < ActiveInteractor::Base
|
686
|
-
after_rollback :print_done
|
687
|
-
|
688
|
-
def perform
|
689
|
-
context.fail!
|
690
|
-
end
|
691
|
-
|
692
|
-
def rollback
|
693
|
-
puts 'Rolling Back'
|
694
|
-
end
|
695
|
-
|
696
|
-
private
|
697
|
-
|
698
|
-
def print_done
|
699
|
-
puts 'Done'
|
700
|
-
end
|
701
|
-
end
|
702
|
-
|
703
|
-
MyInteractor.perform
|
704
|
-
"Rolling Back"
|
705
|
-
"Done"
|
706
|
-
#=> <#MyInteractor::Context...>
|
707
|
-
```
|
708
|
-
|
709
|
-
##### Organizer Callbacks
|
710
|
-
|
711
|
-
We can do worker before `#perform` is invoked on each interactor in an [Organizer](#organizers) with the
|
712
|
-
`.before_each_perform` method:
|
713
|
-
|
714
|
-
```ruby
|
715
|
-
class MyInteractor1 < ActiveInteractor::Base
|
716
|
-
def perform
|
717
|
-
puts 'MyInteractor1'
|
718
|
-
end
|
719
|
-
end
|
720
|
-
|
721
|
-
class MyInteractor2 < ActiveInteractor::Base
|
722
|
-
def perform
|
723
|
-
puts 'MyInteractor2'
|
724
|
-
end
|
725
|
-
end
|
726
|
-
|
727
|
-
class MyOrganizer < ActiveInteractor::Organizer
|
728
|
-
before_each_perform :print_start
|
729
|
-
|
730
|
-
organized MyInteractor1, MyInteractor2
|
731
|
-
|
732
|
-
private
|
733
|
-
|
734
|
-
def print_start
|
735
|
-
puts "Start"
|
736
|
-
end
|
737
|
-
end
|
738
|
-
|
739
|
-
MyOrganizer.perform
|
740
|
-
"Start"
|
741
|
-
"MyInteractor1"
|
742
|
-
"Start"
|
743
|
-
"MyInteractor2"
|
744
|
-
#=> <MyOrganizer::Context...>
|
745
|
-
```
|
746
|
-
|
747
|
-
We can do worker around `#perform` invokation on each interactor in an [Organizer](#organizers) with the
|
748
|
-
`.around_each_perform` method:
|
749
|
-
|
750
|
-
```ruby
|
751
|
-
class MyInteractor1 < ActiveInteractor::Base
|
752
|
-
def perform
|
753
|
-
puts 'MyInteractor1'
|
754
|
-
sleep(1)
|
755
|
-
end
|
756
|
-
end
|
757
|
-
|
758
|
-
class MyInteractor2 < ActiveInteractor::Base
|
759
|
-
def perform
|
760
|
-
puts 'MyInteractor2'
|
761
|
-
sleep(1)
|
762
|
-
end
|
763
|
-
end
|
764
|
-
|
765
|
-
class MyOrganizer < ActiveInteractor::Organizer
|
766
|
-
around_each_perform :print_time
|
767
|
-
|
768
|
-
organized MyInteractor1, MyInteractor2
|
769
|
-
|
770
|
-
private
|
771
|
-
|
772
|
-
def print_time
|
773
|
-
puts Time.now.utc
|
774
|
-
yield
|
775
|
-
puts Time.now.utc
|
776
|
-
end
|
777
|
-
end
|
778
|
-
|
779
|
-
MyOrganizer.perform
|
780
|
-
"2019-01-01 00:00:00 UTC"
|
781
|
-
"MyInteractor1"
|
782
|
-
"2019-01-01 00:00:01 UTC"
|
783
|
-
"2019-01-01 00:00:01 UTC"
|
784
|
-
"MyInteractor2"
|
785
|
-
"2019-01-01 00:00:02 UTC"
|
786
|
-
#=> <MyOrganizer::Context...>
|
787
|
-
```
|
788
|
-
|
789
|
-
We can do worker after `#perform` is invoked on each interactor in an [Organizer](#organizers) with the
|
790
|
-
`.after_each_perform` method:
|
791
|
-
|
792
|
-
```ruby
|
793
|
-
class MyInteractor1 < ActiveInteractor::Base
|
794
|
-
def perform
|
795
|
-
puts 'MyInteractor1'
|
796
|
-
end
|
797
|
-
end
|
798
|
-
|
799
|
-
class MyInteractor2 < ActiveInteractor::Base
|
800
|
-
def perform
|
801
|
-
puts 'MyInteractor2'
|
802
|
-
end
|
803
|
-
end
|
804
|
-
|
805
|
-
class MyOrganizer < ActiveInteractor::Organizer
|
806
|
-
after_each_perform :print_done
|
807
|
-
|
808
|
-
organized MyInteractor1, MyInteractor2
|
809
|
-
|
810
|
-
private
|
811
|
-
|
812
|
-
def print_done
|
813
|
-
puts "Done"
|
814
|
-
end
|
815
|
-
end
|
816
|
-
|
817
|
-
MyOrganizer.perform
|
818
|
-
"MyInteractor1"
|
819
|
-
"Done"
|
820
|
-
"MyInteractor2"
|
821
|
-
"Done"
|
822
|
-
#=> <MyOrganizer::Context...>
|
823
|
-
```
|
824
|
-
|
825
|
-
## Working With Rails
|
826
|
-
|
827
|
-
If you're working with a rails project ActiveInteractor comes bundled with some useful generators
|
828
|
-
to help speed up development. You should first run the install generator with:
|
829
|
-
|
830
|
-
```bash
|
831
|
-
rails generate active_interactor:install [directory]
|
832
|
-
```
|
833
|
-
|
834
|
-
The `directory` option allows you to customize what directory interactors will live in within your
|
835
|
-
application (defaults to 'interactors').
|
836
|
-
|
837
|
-
This will create an initializer a some new classes `ApplicationInteractor`, `ApplicationOrganizer` and
|
838
|
-
`ApplicationContext` in the `app/<directory>` directory.
|
839
|
-
|
840
|
-
### Generators
|
841
|
-
|
842
|
-
ActiveInteractor comes bundled with some rails generators to automatically generate interactors,
|
843
|
-
organizers, and contexts with:
|
844
|
-
|
845
|
-
```bash
|
846
|
-
rails generate interactor MyInteractor
|
847
|
-
```
|
848
|
-
|
849
|
-
```bash
|
850
|
-
rails generate interactor:organizer MyInteractor1 MyInteractor2
|
851
|
-
```
|
852
|
-
|
853
|
-
```bash
|
854
|
-
rails generate interactor:context MyContext
|
855
|
-
```
|
856
|
-
|
857
|
-
These generators will automatically create the approriate classes and matching spec or test files.
|
858
|
-
|
859
|
-
### ActiveRecord Helper Methods
|
860
|
-
|
861
|
-
In some instances you may want to use an `ActiveRecord` model as a context for an interactor. You can
|
862
|
-
do this by calling the `acts_as_context` method on any `ActiveRecord` model, and then simply call the
|
863
|
-
`contextualize_with` method on your interactor or organizer to point it to the approriate class.
|
864
|
-
|
865
|
-
```ruby
|
866
|
-
# app/models/user
|
867
|
-
class User < ApplicationRecord
|
868
|
-
acts_as_context
|
869
|
-
end
|
870
|
-
|
871
|
-
# app/interactors/create_user
|
872
|
-
class CreateUser < ApplicationInteractor
|
873
|
-
contextualize_with :user
|
874
|
-
|
875
|
-
def perform
|
876
|
-
context.email&.downcase!
|
877
|
-
context.save
|
878
|
-
end
|
879
|
-
end
|
880
|
-
|
881
|
-
CreateUser.perform(email: 'HELLO@AARONMALLEN.ME')
|
882
|
-
#=> <#User id=1 email='hello@aaronmallen.me'>
|
883
|
-
```
|
39
|
+
For technical documentation please see the gem's [ruby docs].
|
884
40
|
|
885
41
|
## Development
|
886
42
|
|
@@ -904,13 +60,14 @@ Read our guidelines for [Contributing](CONTRIBUTING.md).
|
|
904
60
|
|
905
61
|
The gem is available as open source under the terms of the [MIT License][mit_license].
|
906
62
|
|
907
|
-
[ActiveModel::Callbacks]: https://api.rubyonrails.org/classes/ActiveModel/Callbacks.html
|
908
|
-
[ActiveModel::Validations]: https://api.rubyonrails.org/classes/ActiveModel/Validations.html
|
909
|
-
[ActiveModel::Validations::Callbacks]: https://api.rubyonrails.org/classes/ActiveModel/Validations/Callbacks.html
|
910
|
-
[collective_idea_interactors]: https://github.com/collectiveidea/interactor
|
911
|
-
[business_logic_wikipedia]: https://en.wikipedia.org/wiki/Business_logic
|
912
63
|
[@collectiveidea]: https://github.com/collectiveidea
|
913
64
|
[@rails]: https://github.com/rails
|
914
65
|
[active_model_git]: https://github.com/rails/rails/tree/master/activemodel
|
915
66
|
[active_support_git]: https://github.com/rails/rails/tree/master/activesupport
|
67
|
+
[ActiveModel::Validations]: https://api.rubyonrails.org/classes/ActiveModel/Validations.html
|
68
|
+
[business_logic_wikipedia]: https://en.wikipedia.org/wiki/Business_logic
|
69
|
+
[collective_idea_interactors]: https://github.com/collectiveidea/interactor
|
70
|
+
[command pattern]: https://en.wikipedia.org/wiki/Command_pattern
|
916
71
|
[mit_license]: https://opensource.org/licenses/MIT
|
72
|
+
[ruby docs]: https://www.rubydoc.info/gems/activeinteractor
|
73
|
+
[wiki]: https://github.com/aaronmallen/activeinteractor/wiki
|