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.
- 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
|
[](https://www.codacy.com/manual/aaronmallen/activeinteractor?utm_source=github.com&utm_medium=referral&utm_content=aaronmallen/activeinteractor&utm_campaign=Badge_Grade)
|
9
9
|
[](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
|