activeinteractor 0.1.7 → 1.0.0.beta.1
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/CHANGELOG.md +36 -1
- data/README.md +397 -395
- data/lib/active_interactor.rb +12 -30
- data/lib/active_interactor/base.rb +18 -8
- data/lib/active_interactor/context.rb +4 -181
- data/lib/active_interactor/context/attributes.rb +37 -149
- data/lib/active_interactor/context/base.rb +141 -0
- data/lib/active_interactor/context/loader.rb +45 -0
- data/lib/active_interactor/error.rb +22 -15
- data/lib/active_interactor/interactor.rb +24 -57
- data/lib/active_interactor/interactor/callbacks.rb +64 -76
- data/lib/active_interactor/interactor/context.rb +97 -63
- data/lib/active_interactor/interactor/worker.rb +22 -65
- data/lib/active_interactor/organizer.rb +180 -164
- data/lib/active_interactor/version.rb +2 -3
- data/lib/rails/generators/active_interactor.rb +2 -37
- data/lib/rails/generators/active_interactor/application_interactor_generator.rb +23 -0
- data/lib/rails/generators/active_interactor/install_generator.rb +8 -12
- data/lib/rails/generators/active_interactor/templates/application_context.rb +4 -0
- data/lib/rails/generators/{templates/application_interactor.erb → active_interactor/templates/application_interactor.rb} +0 -0
- data/lib/rails/generators/active_interactor/templates/application_organizer.rb +4 -0
- data/lib/rails/generators/active_interactor/templates/initializer.erb +5 -0
- data/lib/rails/generators/interactor/context/rspec_generator.rb +19 -0
- data/lib/rails/generators/interactor/context/templates/rspec.erb +7 -0
- data/lib/rails/generators/interactor/context/templates/test_unit.erb +9 -0
- data/lib/rails/generators/interactor/context/test_unit_generator.rb +19 -0
- data/lib/rails/generators/interactor/context_generator.rb +19 -0
- data/lib/rails/generators/interactor/interactor_generator.rb +8 -3
- data/lib/rails/generators/interactor/organizer_generator.rb +8 -3
- data/lib/rails/generators/interactor/rspec_generator.rb +4 -3
- data/lib/rails/generators/interactor/templates/context.erb +4 -0
- data/lib/rails/generators/{templates → interactor/templates}/interactor.erb +0 -0
- data/lib/rails/generators/{templates → interactor/templates}/organizer.erb +1 -1
- data/lib/rails/generators/{templates → interactor/templates}/rspec.erb +0 -0
- data/lib/rails/generators/{templates → interactor/templates}/test_unit.erb +0 -0
- data/lib/rails/generators/interactor/test_unit_generator.rb +4 -3
- data/spec/active_interactor/base_spec.rb +51 -0
- data/spec/active_interactor/context/base_spec.rb +229 -0
- data/spec/active_interactor/error_spec.rb +43 -0
- data/spec/active_interactor/interactor/worker_spec.rb +89 -0
- data/spec/active_interactor/organizer_spec.rb +178 -0
- data/spec/active_interactor_spec.rb +26 -0
- data/spec/integration/basic_callback_integration_spec.rb +355 -0
- data/spec/integration/basic_context_integration_spec.rb +73 -0
- data/spec/integration/basic_integration_spec.rb +220 -0
- data/spec/integration/basic_validations_integration_spec.rb +204 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/support/helpers/factories.rb +41 -0
- data/spec/support/shared_examples/a_class_with_interactor_callback_methods_example.rb +99 -0
- data/spec/support/shared_examples/a_class_with_interactor_context_methods_example.rb +58 -0
- data/spec/support/shared_examples/a_class_with_interactor_methods_example.rb +21 -0
- data/spec/support/shared_examples/a_class_with_organizer_callback_methods_example.rb +39 -0
- data/spec/support/spec_helpers.rb +7 -0
- metadata +68 -138
- data/lib/active_interactor/configuration.rb +0 -38
- data/lib/active_interactor/interactor/execution.rb +0 -24
- data/lib/rails/generators/templates/initializer.erb +0 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f04cfc71e44fdbf89253cb31c1252781a094fa107763c0d27aa853f9d008774e
|
|
4
|
+
data.tar.gz: 2ca6bff9224913bd7420f74387c4d7cec2b704ac45f0d79ab5a4fa64ad46894b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0d106d19d463424f0a406183713dc18ed27d6830fce6b685a96264a6f4396fd9ea18847c819b8dff12639cb319a42bb9ea22a5e5fbfade7e0678fe489a9fe25c
|
|
7
|
+
data.tar.gz: 75e3ef76a19f93ca5f795572403ad10338ef42c23db7e97b582fbe1922db8eafa946da83e02c5b20c129e415feb67b4e7c135c89151fd45a3f415c00e4fd0bf0
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning].
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [v1.0.0-beta.1] - 2020-01-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `ActiveInteractor.logger=`
|
|
15
|
+
- `ActiveInteractor::Base#dup`
|
|
16
|
+
- `ActiveInteractor::Context::Loader`
|
|
17
|
+
- `ActiveInteractor::Error::InvalidContextClass`
|
|
18
|
+
- `ActiveInteractor::Interactor::Context#context_fail!`
|
|
19
|
+
- `ActiveInteractor::Interactor::Context#context_rollback!`
|
|
20
|
+
- `ActiveInteractor::Interactor::Context.contextualize_with`
|
|
21
|
+
- `ActiveInteractor::Interactor::Context#finalize_context!`
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- `ActiveInteractor::Context::Attributes.attributes` now excepts arguments for attributes
|
|
26
|
+
- `ActiveInteractor::Generators` various improvements to rails generators
|
|
27
|
+
- `ActiveInteractor::Interactor::Context.context_class` will now first attempt to find an
|
|
28
|
+
existing context class, and only create a new context class if a context is not found.
|
|
29
|
+
- `ActiveInteractor::Organizer.organize` now excepts symbols and strings as arguments.
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
|
|
33
|
+
- `ActiveInteractor::Configuration`
|
|
34
|
+
- `ActiveInteractor::Context::Attributes.attributes=` in favor of `ActiveInteractor::Context#attributes`
|
|
35
|
+
- `ActiveInteractor::Context::Attributes.attribute_aliases`
|
|
36
|
+
- `ActiveInteractor::Context::Attributes#clean!`
|
|
37
|
+
- `ActiveInteractor::Context::Attributes#keys`
|
|
38
|
+
- `ActiveInteractor::Interactor#fail_on_invalid_context?`
|
|
39
|
+
- `ActiveInteractor::Interactor#should_clean_context?`
|
|
40
|
+
- `ActiveInteractor::Interactor::Callbacks.clean_context_on_completion`
|
|
41
|
+
- `ActiveInteractor::Interactor::Context.context_attribute_aliases`
|
|
42
|
+
- `ActiveInteractor::Interactor::Execution`
|
|
43
|
+
|
|
10
44
|
## [v0.1.7] - 2019-09-10
|
|
11
45
|
|
|
12
46
|
### Fixed
|
|
@@ -74,7 +108,8 @@ and this project adheres to [Semantic Versioning].
|
|
|
74
108
|
|
|
75
109
|
<!-- versions -->
|
|
76
110
|
|
|
77
|
-
[Unreleased]: https://github.com/aaronmallen/activeinteractor/compare/
|
|
111
|
+
[Unreleased]: https://github.com/aaronmallen/activeinteractor/compare/v1.0.0-beta.1..HEAD
|
|
112
|
+
[v1.0.0-beta.1]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.7...v1.0.0-beta.1
|
|
78
113
|
[v0.1.7]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.6...v0.1.7
|
|
79
114
|
[v0.1.6]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.5...v0.1.6
|
|
80
115
|
[v0.1.5]: https://github.com/aaronmallen/activeinteractor/compare/v0.1.4...v0.1.5
|
data/README.md
CHANGED
|
@@ -1,15 +1,45 @@
|
|
|
1
1
|
# ActiveInteractor
|
|
2
2
|
|
|
3
|
-
[](https://rubygems.org/gems/activeinteractor)
|
|
4
|
+
[](https://github.com/aaronmallen/activeinteractor/blob/master/LICENSE)
|
|
5
|
+
[](https://depfu.com/github/aaronmallen/activeinteractor)
|
|
6
6
|
|
|
7
|
-
[](https://github.com/aaronmallen/activeinteractor/actions)
|
|
8
|
+
[](https://codeclimate.com/github/aaronmallen/activeinteractor/maintainability)
|
|
9
|
+
[](https://www.codacy.com/manual/aaronmallen/activeinteractor?utm_source=github.com&utm_medium=referral&utm_content=aaronmallen/activeinteractor&utm_campaign=Badge_Grade)
|
|
10
|
+
[](https://codeclimate.com/github/aaronmallen/activeinteractor/test_coverage)
|
|
10
11
|
|
|
11
12
|
Ruby interactors with [ActiveModel::Validations] based on the [interactor][collective_idea_interactors] gem.
|
|
12
13
|
|
|
14
|
+
<!-- TOC -->
|
|
15
|
+
|
|
16
|
+
* [Getting Started](#getting-started)
|
|
17
|
+
* [What is an Interactor](#what-is-an-interactor)
|
|
18
|
+
* [Usage](#usage)
|
|
19
|
+
* [Context](#context)
|
|
20
|
+
* [Adding to the Context](#adding-to-the-context)
|
|
21
|
+
* [Failing the Context](#failing-the-context)
|
|
22
|
+
* [Dealing with Failure](#dealing-with-failure)
|
|
23
|
+
* [Context Attributes](#context-attributes)
|
|
24
|
+
* [Validating the Context](#validating-the-context)
|
|
25
|
+
* [Using Interactors](#using-interactors)
|
|
26
|
+
* [Kinds of Interactors](#kinds-of-interactors)
|
|
27
|
+
* [Interactors](#interactors)
|
|
28
|
+
* [Organizers](#organizers)
|
|
29
|
+
* [Rollback](#rollback)
|
|
30
|
+
* [Callbacks](#callbacks)
|
|
31
|
+
* [Validation Callbacks](#validation-callbacks)
|
|
32
|
+
* [Perform Callbacks](#perform-callbacks)
|
|
33
|
+
* [Rollback Callbacks](#rollback-callbacks)
|
|
34
|
+
* [Organizer Callbacks](#organizer-callbacks)
|
|
35
|
+
* [Working With Rails](#working-with-rails)
|
|
36
|
+
* [Development](#development)
|
|
37
|
+
* [Contributing](#contributing)
|
|
38
|
+
* [Acknowledgements](#acknowledgements)
|
|
39
|
+
* [License](#license)
|
|
40
|
+
|
|
41
|
+
<!-- TOC -->
|
|
42
|
+
|
|
13
43
|
## Getting Started
|
|
14
44
|
|
|
15
45
|
Add this line to your application's Gemfile:
|
|
@@ -30,31 +60,6 @@ Or install it yourself as:
|
|
|
30
60
|
gem install activeinteractor
|
|
31
61
|
```
|
|
32
62
|
|
|
33
|
-
If you're working with a rails project you will also want to run:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
rails generate active_interactor:install [directory]
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
The `directory` option allows you to customize what directory interactors
|
|
40
|
-
will live in within your application (defaults to 'interactors').
|
|
41
|
-
|
|
42
|
-
This will create an initializer and a new class called `ApplicationInteractor`
|
|
43
|
-
at `app/<interactor directory>/application_interactor.rb`
|
|
44
|
-
|
|
45
|
-
you can then automatically generate interactors and interactor organizers with:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
rails generate interactor MyInteractor
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
rails generate interactor:organizer MyInteractor1 MyInteractor2
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
These two generators will automatically create an interactor class which
|
|
56
|
-
inherits from `ApplicationInteractor` and a matching spec or test file.
|
|
57
|
-
|
|
58
63
|
## What is an Interactor
|
|
59
64
|
|
|
60
65
|
An interactor is a simple, single-purpose service object.
|
|
@@ -67,26 +72,58 @@ Each interactor represents one thing that your application does.
|
|
|
67
72
|
|
|
68
73
|
### Context
|
|
69
74
|
|
|
70
|
-
Each interactor will have it's own immutable
|
|
71
|
-
|
|
75
|
+
Each interactor will have it's own immutable context and context class. All context classes should
|
|
76
|
+
inherit from `ActiveInteractor::Context::Base`. By default an interactor will attempt to find an existing
|
|
77
|
+
class following the naming conventions: `MyInteractor::Context` or `MyInteractorContext`. If no class
|
|
78
|
+
is found a context class will be created using the naming convention `MyInteractor::Context` for example:
|
|
72
79
|
|
|
73
80
|
```ruby
|
|
74
|
-
class MyInteractor < ActiveInteractor::Base
|
|
75
|
-
end
|
|
81
|
+
class MyInteractor < ActiveInteractor::Base; end
|
|
82
|
+
class MyInteractor::Context < ActiveInteractor::Context::Base; end
|
|
83
|
+
|
|
84
|
+
MyInteractor.context_class #=> MyInteractor::Context
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
class MyInteractorContext < ActiveInteractor::Context::Base; end
|
|
89
|
+
class MyInteractor < ActiveInteractor::Base; end
|
|
90
|
+
|
|
91
|
+
MyInteractor.context_class #=> MyInteractorContext
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
class MyInteractor < ActiveInteractor::Base; end
|
|
76
96
|
|
|
77
97
|
MyInteractor.context_class #=> MyInteractor::Context
|
|
78
98
|
```
|
|
79
99
|
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
Additionally you can manually specify a context for an interactor with the `contextualize_with`
|
|
101
|
+
method.
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
class MyGenericContext < ActiveInteractor::Context::Base; end
|
|
105
|
+
|
|
106
|
+
class MyInteractor
|
|
107
|
+
contextualize_with :my_generic_context
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
MyInteractor.context_class #=> MyGenericContext
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
An interactor's context contains everything the interactor needs to do its work. When an interactor does its single purpose,
|
|
114
|
+
it affects its given context.
|
|
82
115
|
|
|
83
116
|
#### Adding to the Context
|
|
84
117
|
|
|
85
|
-
All instances of
|
|
86
|
-
|
|
118
|
+
All instances of context inherit from `OpenStruct`. As an interactor runs it can add information to
|
|
119
|
+
it's context.
|
|
87
120
|
|
|
88
121
|
```ruby
|
|
89
|
-
|
|
122
|
+
class MyInteractor
|
|
123
|
+
def perform
|
|
124
|
+
context.user = User.create(...)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
90
127
|
```
|
|
91
128
|
|
|
92
129
|
#### Failing the Context
|
|
@@ -97,12 +134,12 @@ When something goes wrong in your interactor, you can flag the context as failed
|
|
|
97
134
|
context.fail!
|
|
98
135
|
```
|
|
99
136
|
|
|
100
|
-
When given
|
|
101
|
-
|
|
137
|
+
When given an argument of an instance of `ActiveModel::Errors`, the `#fail!` method can also update the context.
|
|
138
|
+
The following are equivalent:
|
|
102
139
|
|
|
103
140
|
```ruby
|
|
104
141
|
context.errors.merge!(user.errors)
|
|
105
|
-
context.
|
|
142
|
+
context.
|
|
106
143
|
```
|
|
107
144
|
|
|
108
145
|
```ruby
|
|
@@ -112,249 +149,348 @@ context.fail!(user.errors)
|
|
|
112
149
|
You can ask a context if it's a failure:
|
|
113
150
|
|
|
114
151
|
```ruby
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
context.
|
|
152
|
+
class MyInteractor
|
|
153
|
+
def perform
|
|
154
|
+
context.fail!
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
result = MyInteractor.perform
|
|
159
|
+
result.failure? #=> true
|
|
118
160
|
```
|
|
119
161
|
|
|
120
162
|
or if it's a success:
|
|
121
163
|
|
|
122
164
|
```ruby
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
context.
|
|
165
|
+
class MyInteractor
|
|
166
|
+
def perform
|
|
167
|
+
context.user = User.create(...)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
result = MyInteractor.perform
|
|
172
|
+
result.success? #=> true
|
|
126
173
|
```
|
|
127
174
|
|
|
128
175
|
#### Dealing with Failure
|
|
129
176
|
|
|
130
177
|
`context.fail!` always throws an exception of type `ActiveInteractor::Error::ContextFailure`.
|
|
131
178
|
|
|
132
|
-
Normally, however, these exceptions are not seen. In the recommended usage, the consuming
|
|
133
|
-
|
|
134
|
-
the context.
|
|
179
|
+
Normally, however, these exceptions are not seen. In the recommended usage, the consuming object invokes the interactor
|
|
180
|
+
using the class method `perform`, then checks the `success?` method of the context.
|
|
135
181
|
|
|
136
|
-
This works because the
|
|
137
|
-
|
|
182
|
+
This works because the `perform` class method swallows exceptions. When unit testing an interactor, if calling custom business
|
|
183
|
+
logic methods directly and bypassing `perform`, be aware that `fail!` will generate such exceptions.
|
|
138
184
|
|
|
139
185
|
See [Using Interactors](#using-interactors), below, for the recommended usage of `perform` and `success?`.
|
|
140
186
|
|
|
141
187
|
#### Context Attributes
|
|
142
188
|
|
|
143
|
-
Each
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
`context` should have after an interactor has done it's work.
|
|
189
|
+
Each context instance have basic attribute assignment methods which can be invoked directly from the interactor.
|
|
190
|
+
You never need to directly interface with an interactor's context class. Assigning attributes to a context is a
|
|
191
|
+
simple way to explicitly defined what properties a context should have after an interactor has done it's work.
|
|
147
192
|
|
|
148
|
-
You can see what attributes are defined on a given
|
|
193
|
+
You can see what attributes are defined on a given context with the `#attributes` method:
|
|
149
194
|
|
|
150
195
|
```ruby
|
|
151
|
-
class
|
|
152
|
-
|
|
153
|
-
# in the perform method.
|
|
154
|
-
context_attributes :first_name, :last_name, :email, :user
|
|
196
|
+
class MyInteractorContext < ActiveInteractor::Context::Base
|
|
197
|
+
attributes :first_name, :last_name, :email, :user
|
|
155
198
|
end
|
|
156
199
|
|
|
157
|
-
|
|
200
|
+
class MyInteractor < ActiveInteractor::Base; end
|
|
201
|
+
|
|
202
|
+
result = MyInteractor.perform(
|
|
158
203
|
first_name: 'Aaron',
|
|
159
204
|
last_name: 'Allen',
|
|
160
205
|
email: 'hello@aaronmallen.me',
|
|
161
206
|
occupation: 'Software Dude'
|
|
162
207
|
)
|
|
163
|
-
#=>
|
|
208
|
+
#=> <#MyInteractor::Context first_name='Aaron' last_name='Allen' email='hello@aaronmallen.me' occupation='Software Dude'>
|
|
164
209
|
|
|
165
|
-
|
|
166
|
-
|
|
210
|
+
result.attributes #=> { first_name: 'Aaron', last_name: 'Allen', email: 'hello@aaronmallen.me' }
|
|
211
|
+
result.occupation #=> 'Software Dude'
|
|
167
212
|
```
|
|
168
213
|
|
|
169
|
-
|
|
170
|
-
regardless of whether or not the properties are defined in a `context#attributes`:
|
|
214
|
+
#### Validating the Context
|
|
171
215
|
|
|
172
|
-
|
|
173
|
-
context.
|
|
174
|
-
|
|
216
|
+
ActiveInteractor delegates all the validation methods provided by [ActiveModel::Validations] onto an interactor's
|
|
217
|
+
context class from the interactor itself. All of the methods found in [ActiveModel::Validations] can be invoked directly
|
|
218
|
+
on your interactor with the prefix `context_`. However this can be confusing and it is recommended to make all validation
|
|
219
|
+
calls on a context class directly.
|
|
175
220
|
|
|
176
|
-
|
|
177
|
-
|
|
221
|
+
ActiveInteractor provides two validation callback steps:
|
|
222
|
+
|
|
223
|
+
* `:calling` used before `#perform` is invoked on an interactor
|
|
224
|
+
* `:called` used after `#perform` is invoked on an interactor
|
|
225
|
+
|
|
226
|
+
A basic implementation might look like this:
|
|
178
227
|
|
|
179
228
|
```ruby
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
229
|
+
class MyInteractorContext < ActiveInteractor::Context::Base
|
|
230
|
+
attributes :first_name, :last_name, :email, :user
|
|
231
|
+
# only validates presence before perform is invoked
|
|
232
|
+
validates :first_name, presence: true, on: :calling
|
|
233
|
+
# validates before and after perform is invoked
|
|
234
|
+
validates :email, presence: true,
|
|
235
|
+
format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
236
|
+
# validates after perform is invoked
|
|
237
|
+
validates :user, presence: true, on: :called
|
|
238
|
+
validate :user_is_a_user, on: :called
|
|
183
239
|
|
|
184
|
-
|
|
240
|
+
private
|
|
185
241
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
242
|
+
def user_is_a_user
|
|
243
|
+
return if user.is_a?(User)
|
|
244
|
+
|
|
245
|
+
errors.add(:user, :invalid)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
190
248
|
|
|
191
|
-
```ruby
|
|
192
249
|
class MyInteractor < ActiveInteractor::Base
|
|
193
|
-
|
|
194
|
-
|
|
250
|
+
def perform
|
|
251
|
+
context.user = User.create_with(
|
|
252
|
+
first_name: context.first_name,
|
|
253
|
+
last_name: context.last_name
|
|
254
|
+
).find_or_create_by(email: context.email)
|
|
255
|
+
end
|
|
195
256
|
end
|
|
196
257
|
|
|
197
|
-
|
|
198
|
-
|
|
258
|
+
result = MyInteractor.perform(last_name: 'Allen')
|
|
259
|
+
#=> <#MyInteractor::Context last_name='Allen>
|
|
260
|
+
result.failure? #=> true
|
|
261
|
+
result.valid? #=> false
|
|
262
|
+
result.errors[:first_name] #=> ['can not be blank']
|
|
263
|
+
|
|
264
|
+
result = MyInterator.perform(first_name: 'Aaron', email: 'hello@aaronmallen.me')
|
|
265
|
+
#=> <#MyInteractor::Context first_name='Aaron' email='hello@aaronmallen.me' user=<#User ...>>
|
|
266
|
+
result.success? #=> true
|
|
267
|
+
result.valid? #=> true
|
|
268
|
+
result.errors.empty? #=> true
|
|
199
269
|
```
|
|
200
270
|
|
|
201
|
-
|
|
271
|
+
### Using Interactors
|
|
272
|
+
|
|
273
|
+
Most of the time, your application will use its interactors from its controllers. The following controller:
|
|
202
274
|
|
|
203
275
|
```ruby
|
|
204
|
-
class
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
276
|
+
class SessionsController < ApplicationController
|
|
277
|
+
def create
|
|
278
|
+
if user = User.authenticate(session_params[:email], session_params[:password])
|
|
279
|
+
session[:user_token] = user.secret_token
|
|
280
|
+
redirect_to user
|
|
281
|
+
else
|
|
282
|
+
flash.now[:message] = "Please try again."
|
|
283
|
+
render :new
|
|
284
|
+
end
|
|
285
|
+
end
|
|
208
286
|
|
|
209
|
-
|
|
210
|
-
# => <#MyInteractor::Context first_name='Aaron', last_name='Allen'>
|
|
287
|
+
private
|
|
211
288
|
|
|
212
|
-
|
|
213
|
-
|
|
289
|
+
def session_params
|
|
290
|
+
params.require(:session).permit(:email, :password)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
214
293
|
```
|
|
215
294
|
|
|
216
|
-
|
|
295
|
+
can be refactored to:
|
|
296
|
+
|
|
297
|
+
```ruby
|
|
298
|
+
class SessionsController < ApplicationController
|
|
299
|
+
def create
|
|
300
|
+
result = AuthenticateUser.perform(session_params)
|
|
217
301
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
302
|
+
if result.success?
|
|
303
|
+
session[:user_token] = result.token
|
|
304
|
+
redirect_to result.user
|
|
305
|
+
else
|
|
306
|
+
flash.now[:message] = t(result.errors.full_messages)
|
|
307
|
+
render :new
|
|
308
|
+
end
|
|
309
|
+
end
|
|
221
310
|
|
|
222
|
-
|
|
311
|
+
private
|
|
223
312
|
|
|
224
|
-
|
|
225
|
-
|
|
313
|
+
def session_params
|
|
314
|
+
params.require(:session).permit(:email, :password)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
```
|
|
226
318
|
|
|
227
|
-
|
|
319
|
+
given the basic interactor and context:
|
|
228
320
|
|
|
229
321
|
```ruby
|
|
230
|
-
class
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
# validates after perform is invoked
|
|
238
|
-
context_validates :user, presence: true, on: :called
|
|
239
|
-
context_validate :user_is_a_user, on: :called
|
|
322
|
+
class AuthenticateUserContext < ActiveInteractor::Context::Base
|
|
323
|
+
attributes :email, :password, :user, :token
|
|
324
|
+
validates :email, presence: true,
|
|
325
|
+
format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
326
|
+
validates :password, presence: true
|
|
327
|
+
validates :user, presence: true, on: :called
|
|
328
|
+
end
|
|
240
329
|
|
|
330
|
+
class AuthenticateUser < ActiveInteractor::Base
|
|
241
331
|
def perform
|
|
242
|
-
context.user = User.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
)
|
|
332
|
+
context.user = User.authenticate(
|
|
333
|
+
context.email,
|
|
334
|
+
context.password
|
|
335
|
+
)
|
|
336
|
+
context.token = context.user.secret_token
|
|
246
337
|
end
|
|
338
|
+
end
|
|
339
|
+
```
|
|
247
340
|
|
|
248
|
-
|
|
341
|
+
The `perform` class method is the proper way to invoke an interactor. The hash argument is converted to the interactor instance's
|
|
342
|
+
context. The `perform` instance method is invoked along with any callbacks and validations that the interactor might define.
|
|
343
|
+
Finally, the context (along with any changes made to it) is returned.
|
|
249
344
|
|
|
250
|
-
|
|
251
|
-
return if context.user.is_a?(User)
|
|
345
|
+
#### Kinds of Interactors
|
|
252
346
|
|
|
253
|
-
|
|
254
|
-
end
|
|
255
|
-
end
|
|
347
|
+
There are two kinds of interactors built into the Interactor library: basic interactors and organizers.
|
|
256
348
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
context.valid? #=> false
|
|
261
|
-
context.errors[:first_name] #=> ['can not be blank']
|
|
349
|
+
#### Interactors
|
|
350
|
+
|
|
351
|
+
A basic interactor is a class that includes Interactor and defines `perform`.\
|
|
262
352
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
context.
|
|
267
|
-
|
|
353
|
+
```ruby
|
|
354
|
+
class AuthenticateUser < ActiveInteractor::Base
|
|
355
|
+
def perform
|
|
356
|
+
user = User.authenticate(context.email, context.password)
|
|
357
|
+
if user
|
|
358
|
+
context.user = user
|
|
359
|
+
context.token = user.secret_token
|
|
360
|
+
else
|
|
361
|
+
context.fail!
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
268
365
|
```
|
|
269
366
|
|
|
270
|
-
|
|
367
|
+
Basic interactors are the building blocks. They are your application's single-purpose units of work.
|
|
271
368
|
|
|
272
|
-
|
|
273
|
-
on context validation, `perform`, and `rollback`. Callbacks can be defined with a `block`,
|
|
274
|
-
`Proc`, or `Symbol` method name and take the same conditional arguments outlined
|
|
275
|
-
in those two modules.
|
|
369
|
+
#### Organizers
|
|
276
370
|
|
|
277
|
-
|
|
278
|
-
will first attempt to invoke the method on itself, if it cannot find the defined
|
|
279
|
-
method it will attempt to invoke it on the interactor. Be concious of scope
|
|
280
|
-
when defining these methods.
|
|
371
|
+
An organizer is an important variation on the basic interactor. Its single purpose is to run other interactors.
|
|
281
372
|
|
|
282
|
-
|
|
373
|
+
```ruby
|
|
374
|
+
class CreateOrder < ActiveInteractor::Base
|
|
375
|
+
def perform
|
|
376
|
+
...
|
|
377
|
+
end
|
|
378
|
+
end
|
|
283
379
|
|
|
284
|
-
|
|
380
|
+
class ChargeCard < ActiveInteractor::Base
|
|
381
|
+
def perform
|
|
382
|
+
...
|
|
383
|
+
end
|
|
384
|
+
end
|
|
285
385
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
before_context_validation { last_name ||= 'Unknown' }
|
|
386
|
+
class SendThankYou < ActiveInteractor::Base
|
|
387
|
+
def perform
|
|
388
|
+
...
|
|
389
|
+
end
|
|
291
390
|
end
|
|
292
391
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
392
|
+
class PlaceOrder < ActiveInteractor::Organizer
|
|
393
|
+
|
|
394
|
+
organize :create_order, :charge_card, :send_thank_you
|
|
395
|
+
end
|
|
296
396
|
```
|
|
297
397
|
|
|
298
|
-
|
|
398
|
+
In the controller, you can run the `PlaceOrder` organizer just like you would any other interactor:
|
|
299
399
|
|
|
300
400
|
```ruby
|
|
301
|
-
class
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
401
|
+
class OrdersController < ApplicationController
|
|
402
|
+
def create
|
|
403
|
+
result = PlaceOrder.perform(order_params: order_params)
|
|
404
|
+
|
|
405
|
+
if result.success?
|
|
406
|
+
redirect_to result.order
|
|
407
|
+
else
|
|
408
|
+
@order = result.order
|
|
409
|
+
render :new
|
|
410
|
+
end
|
|
411
|
+
end
|
|
306
412
|
|
|
307
413
|
private
|
|
308
414
|
|
|
309
|
-
def
|
|
310
|
-
|
|
415
|
+
def order_params
|
|
416
|
+
params.require(:order).permit!
|
|
311
417
|
end
|
|
312
418
|
end
|
|
419
|
+
```
|
|
313
420
|
|
|
314
|
-
context
|
|
315
|
-
context
|
|
421
|
+
The organizer passes its context to the interactors that it organizes, one at a time and in order. Each interactor may
|
|
422
|
+
change that context before it's passed along to the next interactor.
|
|
423
|
+
|
|
424
|
+
#### Rollback
|
|
425
|
+
|
|
426
|
+
If any one of the organized interactors fails its context, the organizer stops. If the `ChargeCard` interactor fails,
|
|
427
|
+
`SendThankYou` is never called.
|
|
428
|
+
|
|
429
|
+
In addition, any interactors that had already run are given the chance to undo themselves, in reverse order.
|
|
430
|
+
Simply define the rollback method on your interactors:
|
|
431
|
+
|
|
432
|
+
```ruby
|
|
433
|
+
class CreateOrder < ActiveInteractor::Base
|
|
434
|
+
def perform
|
|
435
|
+
order = Order.create(order_params)
|
|
436
|
+
|
|
437
|
+
if order.persisted?
|
|
438
|
+
context.order = order
|
|
439
|
+
else
|
|
440
|
+
context.fail!
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def rollback
|
|
445
|
+
context.order.destroy
|
|
446
|
+
end
|
|
447
|
+
end
|
|
316
448
|
```
|
|
317
449
|
|
|
318
|
-
|
|
319
|
-
|
|
450
|
+
#### Callbacks
|
|
451
|
+
|
|
452
|
+
ActiveInteractor uses [ActiveModel::Callbacks] and [ActiveModel::Validations::Callbacks] on context validation, perform,
|
|
453
|
+
and rollback. Callbacks can be defined with a `block`, `Proc`, or `Symbol` method name and take the same conditional arguments
|
|
454
|
+
outlined in those two modules.
|
|
455
|
+
|
|
456
|
+
##### Validation Callbacks
|
|
457
|
+
|
|
458
|
+
We can do work before an interactor's context is validated with the `before_context_validation` method:
|
|
320
459
|
|
|
321
460
|
```ruby
|
|
461
|
+
class MyInteractorContext < ActiveInteractor::Context::Base
|
|
462
|
+
attributes :first_name, :last_name, :email
|
|
463
|
+
validates :last_name, presence: true
|
|
464
|
+
end
|
|
465
|
+
|
|
322
466
|
class MyInteractor < ActiveInteractor::Base
|
|
323
|
-
|
|
324
|
-
context_attributes :first_name, :last_name, :email, :user
|
|
325
|
-
context_validates :first_name, presence: true
|
|
467
|
+
before_context_validation { context.last_name ||= 'Unknown' }
|
|
326
468
|
end
|
|
327
469
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
470
|
+
result = MyInteractor.perform(first_name: 'Aaron', email: 'hello@aaronmallen.me')
|
|
471
|
+
result.valid? #=> true
|
|
472
|
+
result.last_name #=> 'Unknown'
|
|
331
473
|
```
|
|
332
474
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
We can ensure only properties in the context's `attributes` are
|
|
336
|
-
returned after `perform` is invoked with the `clean_context_on_completion`
|
|
337
|
-
class method:
|
|
475
|
+
We can do work after an interactor's context is validated with the `after_context_validation` method:
|
|
338
476
|
|
|
339
477
|
```ruby
|
|
340
|
-
class
|
|
341
|
-
|
|
342
|
-
|
|
478
|
+
class MyInteractorContext < ActiveInteractor::Context::Base
|
|
479
|
+
attributes :first_name, :last_name, :email
|
|
480
|
+
validates :email, presence: true,
|
|
481
|
+
format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
482
|
+
end
|
|
343
483
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
occupation: context.occupation
|
|
347
|
-
).find_or_create_by(email: context.email)
|
|
348
|
-
end
|
|
484
|
+
class MyInteractor < ActiveInteractor::Base
|
|
485
|
+
after_context_validation { context.email&.downcase! }
|
|
349
486
|
end
|
|
350
487
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
context.user #=> <#User email='hello@aaronmallen.me', occupation='Software Dude'>
|
|
488
|
+
result = MyInteractor.perform(first_name: 'Aaron', last_name: 'Allen', email: 'HELLO@AARONMALLEN.ME')
|
|
489
|
+
result.valid? #=> true
|
|
490
|
+
result.email #=> 'hello@aaronmallen.me'
|
|
355
491
|
```
|
|
356
492
|
|
|
357
|
-
|
|
493
|
+
##### Perform Callbacks
|
|
358
494
|
|
|
359
495
|
We can do work before `perform` is invoked with the `before_perform` method:
|
|
360
496
|
|
|
@@ -373,17 +509,21 @@ class MyInteractor < ActiveInteractor::Base
|
|
|
373
509
|
end
|
|
374
510
|
end
|
|
375
511
|
|
|
376
|
-
|
|
512
|
+
MyInteractor.perform
|
|
377
513
|
"Start"
|
|
378
514
|
"Performing"
|
|
515
|
+
#=> <#MyInteractor::Context...>
|
|
379
516
|
```
|
|
380
517
|
|
|
381
518
|
We can do work around `perform` invokation with the `around_perform` method:
|
|
382
519
|
|
|
383
520
|
```ruby
|
|
384
521
|
class MyInteractor < ActiveInteractor::Base
|
|
385
|
-
|
|
386
|
-
|
|
522
|
+
around_perform :track_time
|
|
523
|
+
|
|
524
|
+
def perform
|
|
525
|
+
sleep(1)
|
|
526
|
+
end
|
|
387
527
|
|
|
388
528
|
private
|
|
389
529
|
|
|
@@ -394,14 +534,9 @@ class MyInteractor < ActiveInteractor::Base
|
|
|
394
534
|
end
|
|
395
535
|
end
|
|
396
536
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
context = MyInteractor.perform
|
|
402
|
-
context.valid? #=> false
|
|
403
|
-
context.start_time #=> nil
|
|
404
|
-
context.end_time # #=> nil
|
|
537
|
+
result = MyInteractor.perform
|
|
538
|
+
result.start_time #=> 2019-01-01 00:00:00 UTC
|
|
539
|
+
result.end_time #=> 2019-01-01 00:00:01 UTC
|
|
405
540
|
```
|
|
406
541
|
|
|
407
542
|
We can do work after `perform` is invoked with the `after_perform` method:
|
|
@@ -421,12 +556,13 @@ class MyInteractor < ActiveInteractor::Base
|
|
|
421
556
|
end
|
|
422
557
|
end
|
|
423
558
|
|
|
424
|
-
|
|
559
|
+
MyInteractor.perform
|
|
425
560
|
"Performing"
|
|
426
561
|
"Done"
|
|
562
|
+
#=> <#MyInteractor::Context...>
|
|
427
563
|
```
|
|
428
564
|
|
|
429
|
-
|
|
565
|
+
##### Rollback Callbacks
|
|
430
566
|
|
|
431
567
|
We can do work before `rollback` is invoked with the `before_rollback` method:
|
|
432
568
|
|
|
@@ -434,6 +570,10 @@ We can do work before `rollback` is invoked with the `before_rollback` method:
|
|
|
434
570
|
class MyInteractor < ActiveInteractor::Base
|
|
435
571
|
before_rollback :print_start
|
|
436
572
|
|
|
573
|
+
def perform
|
|
574
|
+
context.fail!
|
|
575
|
+
end
|
|
576
|
+
|
|
437
577
|
def rollback
|
|
438
578
|
puts 'Rolling Back'
|
|
439
579
|
end
|
|
@@ -445,10 +585,10 @@ class MyInteractor < ActiveInteractor::Base
|
|
|
445
585
|
end
|
|
446
586
|
end
|
|
447
587
|
|
|
448
|
-
|
|
449
|
-
context.rollback!
|
|
588
|
+
MyInteractor.perform
|
|
450
589
|
"Start"
|
|
451
590
|
"Rolling Back"
|
|
591
|
+
#=> <#MyInteractor::Context...>
|
|
452
592
|
```
|
|
453
593
|
|
|
454
594
|
We can do work around `rollback` invokation with the `around_rollback` method:
|
|
@@ -457,6 +597,14 @@ We can do work around `rollback` invokation with the `around_rollback` method:
|
|
|
457
597
|
class MyInteractor < ActiveInteractor::Base
|
|
458
598
|
around_rollback :track_time
|
|
459
599
|
|
|
600
|
+
def perform
|
|
601
|
+
context.fail!
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
def rollback
|
|
605
|
+
sleep(1)
|
|
606
|
+
end
|
|
607
|
+
|
|
460
608
|
private
|
|
461
609
|
|
|
462
610
|
def track_time
|
|
@@ -466,10 +614,9 @@ class MyInteractor < ActiveInteractor::Base
|
|
|
466
614
|
end
|
|
467
615
|
end
|
|
468
616
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
context.end_time # #=> 2019-01-01 00:00:01 UTC
|
|
617
|
+
result = MyInteractor.perform
|
|
618
|
+
result.start_time #=> 2019-01-01 00:00:00 UTC
|
|
619
|
+
result.end_time #=> 2019-01-01 00:00:01 UTC
|
|
473
620
|
```
|
|
474
621
|
|
|
475
622
|
We can do work after `rollback` is invoked with the `after_rollback` method:
|
|
@@ -478,6 +625,10 @@ We can do work after `rollback` is invoked with the `after_rollback` method:
|
|
|
478
625
|
class MyInteractor < ActiveInteractor::Base
|
|
479
626
|
after_rollback :print_done
|
|
480
627
|
|
|
628
|
+
def perform
|
|
629
|
+
context.fail!
|
|
630
|
+
end
|
|
631
|
+
|
|
481
632
|
def rollback
|
|
482
633
|
puts 'Rolling Back'
|
|
483
634
|
end
|
|
@@ -489,29 +640,25 @@ class MyInteractor < ActiveInteractor::Base
|
|
|
489
640
|
end
|
|
490
641
|
end
|
|
491
642
|
|
|
492
|
-
|
|
493
|
-
context.rollback!
|
|
643
|
+
MyInteractor.perform
|
|
494
644
|
"Rolling Back"
|
|
495
645
|
"Done"
|
|
646
|
+
#=> <#MyInteractor::Context...>
|
|
496
647
|
```
|
|
497
648
|
|
|
498
|
-
|
|
649
|
+
##### Organizer Callbacks
|
|
499
650
|
|
|
500
|
-
We can do worker before `perform` is invoked on each interactor in an [Organizer](#organizers)
|
|
501
|
-
|
|
651
|
+
We can do worker before `perform` is invoked on each interactor in an [Organizer](#organizers) with the
|
|
652
|
+
`before_each_perform` method:
|
|
502
653
|
|
|
503
654
|
```ruby
|
|
504
|
-
|
|
505
|
-
before_perform :print_name
|
|
506
|
-
|
|
655
|
+
class MyInteractor1 < ActiveInteractor::Base
|
|
507
656
|
def perform
|
|
508
657
|
puts 'MyInteractor1'
|
|
509
658
|
end
|
|
510
659
|
end
|
|
511
660
|
|
|
512
661
|
class MyInteractor2 < ActiveInteractor::Base
|
|
513
|
-
before_perform :print_name
|
|
514
|
-
|
|
515
662
|
def perform
|
|
516
663
|
puts 'MyInteractor2'
|
|
517
664
|
end
|
|
@@ -529,31 +676,29 @@ class MyOrganizer < ActiveInteractor::Organizer
|
|
|
529
676
|
end
|
|
530
677
|
end
|
|
531
678
|
|
|
532
|
-
MyOrganizer.perform
|
|
679
|
+
MyOrganizer.perform
|
|
533
680
|
"Start"
|
|
534
681
|
"MyInteractor1"
|
|
535
682
|
"Start"
|
|
536
683
|
"MyInteractor2"
|
|
537
|
-
#=> <MyOrganizer::Context
|
|
684
|
+
#=> <MyOrganizer::Context...>
|
|
538
685
|
```
|
|
539
686
|
|
|
540
|
-
We can do worker around `perform` is invokation on each interactor in an [Organizer](#organizers)
|
|
541
|
-
|
|
687
|
+
We can do worker around `perform` is invokation on each interactor in an [Organizer](#organizers) with the
|
|
688
|
+
`around_each_perform` method:
|
|
542
689
|
|
|
543
690
|
```ruby
|
|
544
691
|
class MyInteractor1 < ActiveInteractor::Base
|
|
545
|
-
before_perform :print_name
|
|
546
|
-
|
|
547
692
|
def perform
|
|
548
693
|
puts 'MyInteractor1'
|
|
694
|
+
sleep(1)
|
|
549
695
|
end
|
|
550
696
|
end
|
|
551
697
|
|
|
552
698
|
class MyInteractor2 < ActiveInteractor::Base
|
|
553
|
-
before_perform :print_name
|
|
554
|
-
|
|
555
699
|
def perform
|
|
556
700
|
puts 'MyInteractor2'
|
|
701
|
+
sleep(1)
|
|
557
702
|
end
|
|
558
703
|
end
|
|
559
704
|
|
|
@@ -571,31 +716,27 @@ class MyOrganizer < ActiveInteractor::Organizer
|
|
|
571
716
|
end
|
|
572
717
|
end
|
|
573
718
|
|
|
574
|
-
MyOrganizer.perform
|
|
575
|
-
"2019-
|
|
719
|
+
MyOrganizer.perform
|
|
720
|
+
"2019-01-01 00:00:00 UTC"
|
|
576
721
|
"MyInteractor1"
|
|
577
|
-
"2019-
|
|
578
|
-
"2019-
|
|
722
|
+
"2019-01-01 00:00:01 UTC"
|
|
723
|
+
"2019-01-01 00:00:01 UTC"
|
|
579
724
|
"MyInteractor2"
|
|
580
|
-
"2019-
|
|
581
|
-
#=> <MyOrganizer::Context
|
|
725
|
+
"2019-01-01 00:00:02 UTC"
|
|
726
|
+
#=> <MyOrganizer::Context...>
|
|
582
727
|
```
|
|
583
728
|
|
|
584
|
-
We can do worker after `perform` is invoked on each interactor in an [Organizer](#organizers)
|
|
585
|
-
|
|
729
|
+
We can do worker after `perform` is invoked on each interactor in an [Organizer](#organizers) with the
|
|
730
|
+
`after_each_perform` method:
|
|
586
731
|
|
|
587
732
|
```ruby
|
|
588
733
|
class MyInteractor1 < ActiveInteractor::Base
|
|
589
|
-
before_perform :print_name
|
|
590
|
-
|
|
591
734
|
def perform
|
|
592
735
|
puts 'MyInteractor1'
|
|
593
736
|
end
|
|
594
737
|
end
|
|
595
738
|
|
|
596
739
|
class MyInteractor2 < ActiveInteractor::Base
|
|
597
|
-
before_perform :print_name
|
|
598
|
-
|
|
599
740
|
def perform
|
|
600
741
|
puts 'MyInteractor2'
|
|
601
742
|
end
|
|
@@ -609,182 +750,45 @@ class MyOrganizer < ActiveInteractor::Organizer
|
|
|
609
750
|
private
|
|
610
751
|
|
|
611
752
|
def print_done
|
|
612
|
-
puts "
|
|
753
|
+
puts "Done"
|
|
613
754
|
end
|
|
614
755
|
end
|
|
615
756
|
|
|
616
|
-
MyOrganizer.perform
|
|
757
|
+
MyOrganizer.perform
|
|
617
758
|
"MyInteractor1"
|
|
618
759
|
"Done"
|
|
619
760
|
"MyInteractor2"
|
|
620
761
|
"Done"
|
|
621
|
-
#=> <MyOrganizer::Context
|
|
762
|
+
#=> <MyOrganizer::Context...>
|
|
622
763
|
```
|
|
623
764
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
Most of the time, your application will use its interactors from its controllers. The following controller:
|
|
627
|
-
|
|
628
|
-
```ruby
|
|
629
|
-
class SessionsController < ApplicationController
|
|
630
|
-
def create
|
|
631
|
-
if user = User.authenticate(session_params[:email], session_params[:password])
|
|
632
|
-
session[:user_token] = user.secret_token
|
|
633
|
-
redirect_to user
|
|
634
|
-
else
|
|
635
|
-
flash.now[:message] = "Please try again."
|
|
636
|
-
render :new
|
|
637
|
-
end
|
|
638
|
-
end
|
|
765
|
+
## Working With Rails
|
|
639
766
|
|
|
640
|
-
|
|
767
|
+
If you're working with a rails project ActiveInteractor comes bundled with some useful generators
|
|
768
|
+
to help speed up development. You should first run the install generator with:
|
|
641
769
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
end
|
|
645
|
-
end
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
can be refactored to:
|
|
649
|
-
|
|
650
|
-
```ruby
|
|
651
|
-
class SessionsController < ApplicationController
|
|
652
|
-
def create
|
|
653
|
-
result = AuthenticateUser.perform(session_params)
|
|
654
|
-
|
|
655
|
-
if result.success?
|
|
656
|
-
session[:user_token] = result.token
|
|
657
|
-
redirect_to result.user
|
|
658
|
-
else
|
|
659
|
-
flash.now[:message] = t(result.errors.full_messages)
|
|
660
|
-
render :new
|
|
661
|
-
end
|
|
662
|
-
end
|
|
663
|
-
|
|
664
|
-
private
|
|
665
|
-
|
|
666
|
-
def session_params
|
|
667
|
-
params.require(:session).permit(:email, :password)
|
|
668
|
-
end
|
|
669
|
-
end
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
given the basic interactor:
|
|
673
|
-
|
|
674
|
-
```ruby
|
|
675
|
-
class AuthenticateUser < ActiveInteractor::Base
|
|
676
|
-
context_attributes :email, :password, :user, :token
|
|
677
|
-
context_validates :email, presence: true,
|
|
678
|
-
format: { with: URI::MailTo::EMAIL_REGEXP }
|
|
679
|
-
context_validates :password, presence: true
|
|
680
|
-
context_validates :user, presence: true, on: :called
|
|
681
|
-
|
|
682
|
-
def perform
|
|
683
|
-
context.user = User.authenticate(
|
|
684
|
-
context.email,
|
|
685
|
-
context.password
|
|
686
|
-
)
|
|
687
|
-
context.token = context.user.secret_token
|
|
688
|
-
end
|
|
689
|
-
end
|
|
770
|
+
```bash
|
|
771
|
+
rails generate active_interactor:install
|
|
690
772
|
```
|
|
691
773
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
The `preform` instance method is invoked along with any callbacks and validations
|
|
695
|
-
that the interactor might define. Finally, the context (along with any changes made to it)
|
|
696
|
-
is returned.
|
|
697
|
-
|
|
698
|
-
### Kinds of Interactors
|
|
774
|
+
This will create an initializer a some new classes `ApplicationInteractor`, `ApplicationOrganizer` and
|
|
775
|
+
`ApplicationContext` in the `app/interactors` directory.
|
|
699
776
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
#### Interactors
|
|
777
|
+
You can then automatically generate interactors, organizers, and contexts with:
|
|
703
778
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
```ruby
|
|
707
|
-
class AuthenticateUser
|
|
708
|
-
include Interactor
|
|
709
|
-
|
|
710
|
-
def perform
|
|
711
|
-
if user = User.authenticate(context.email, context.password)
|
|
712
|
-
context.user = user
|
|
713
|
-
context.token = user.secret_token
|
|
714
|
-
else
|
|
715
|
-
context.fail!
|
|
716
|
-
end
|
|
717
|
-
end
|
|
718
|
-
end
|
|
779
|
+
```bash
|
|
780
|
+
rails generate interactor MyInteractor
|
|
719
781
|
```
|
|
720
782
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
#### Organizers
|
|
724
|
-
|
|
725
|
-
An organizer is an important variation on the basic interactor. Its single purpose is to run other interactors.
|
|
726
|
-
|
|
727
|
-
```ruby
|
|
728
|
-
class PlaceOrder
|
|
729
|
-
include Interactor::Organizer
|
|
730
|
-
|
|
731
|
-
organize CreateOrder, ChargeCard, SendThankYou
|
|
732
|
-
end
|
|
783
|
+
```bash
|
|
784
|
+
rails generate interactor:organizer MyInteractor1 MyInteractor2
|
|
733
785
|
```
|
|
734
786
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
```ruby
|
|
738
|
-
class OrdersController < ApplicationController
|
|
739
|
-
def create
|
|
740
|
-
result = PlaceOrder.call(order_params: order_params)
|
|
741
|
-
|
|
742
|
-
if result.success?
|
|
743
|
-
redirect_to result.order
|
|
744
|
-
else
|
|
745
|
-
@order = result.order
|
|
746
|
-
render :new
|
|
747
|
-
end
|
|
748
|
-
end
|
|
749
|
-
|
|
750
|
-
private
|
|
751
|
-
|
|
752
|
-
def order_params
|
|
753
|
-
params.require(:order).permit!
|
|
754
|
-
end
|
|
755
|
-
end
|
|
787
|
+
```bash
|
|
788
|
+
rails generate interactor:context MyContext
|
|
756
789
|
```
|
|
757
790
|
|
|
758
|
-
|
|
759
|
-
Each interactor may change that context before it's passed along to the next interactor.
|
|
760
|
-
|
|
761
|
-
#### Rollback
|
|
762
|
-
|
|
763
|
-
If any one of the organized interactors fails its context, the organizer stops.
|
|
764
|
-
If the `ChargeCard` interactor fails, `SendThankYou` is never called.
|
|
765
|
-
|
|
766
|
-
In addition, any interactors that had already run are given the chance to undo themselves, in reverse order.
|
|
767
|
-
Simply define the rollback method on your interactors:
|
|
768
|
-
|
|
769
|
-
```ruby
|
|
770
|
-
class CreateOrder
|
|
771
|
-
include Interactor
|
|
772
|
-
|
|
773
|
-
def perform
|
|
774
|
-
order = Order.create(order_params)
|
|
775
|
-
|
|
776
|
-
if order.persisted?
|
|
777
|
-
context.order = order
|
|
778
|
-
else
|
|
779
|
-
context.fail!
|
|
780
|
-
end
|
|
781
|
-
end
|
|
782
|
-
|
|
783
|
-
def rollback
|
|
784
|
-
context.order.destroy
|
|
785
|
-
end
|
|
786
|
-
end
|
|
787
|
-
```
|
|
791
|
+
These generators will automatically create the approriate classes and matching spec or test files.
|
|
788
792
|
|
|
789
793
|
## Development
|
|
790
794
|
|
|
@@ -793,8 +797,6 @@ You can also run `bin/console` for an interactive prompt that will allow you to
|
|
|
793
797
|
|
|
794
798
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
795
799
|
|
|
796
|
-
Additionally you can run tests in both rails 2.5 and rails 2.6 with `bin/test`.
|
|
797
|
-
|
|
798
800
|
## Contributing
|
|
799
801
|
|
|
800
802
|
Read our guidelines for [Contributing](CONTRIBUTING.md).
|