activeinteractor 0.1.7 → 1.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Version](https://img.shields.io/gem/v/activeinteractor.svg?logo=ruby
|
4
|
-
[![License](https://img.shields.io/github/license/aaronmallen/activeinteractor.svg?maxAge=300
|
5
|
-
[![Dependencies](https://img.shields.io/depfu/aaronmallen/activeinteractor.svg?maxAge=300
|
3
|
+
[![Version](https://img.shields.io/gem/v/activeinteractor.svg?logo=ruby)](https://rubygems.org/gems/activeinteractor)
|
4
|
+
[![License](https://img.shields.io/github/license/aaronmallen/activeinteractor.svg?maxAge=300)](https://github.com/aaronmallen/activeinteractor/blob/master/LICENSE)
|
5
|
+
[![Dependencies](https://img.shields.io/depfu/aaronmallen/activeinteractor.svg?maxAge=300)](https://depfu.com/github/aaronmallen/activeinteractor)
|
6
6
|
|
7
|
-
[![Build Status](https://
|
8
|
-
[![Maintainability](https://img.shields.io/codeclimate/maintainability/aaronmallen/activeinteractor.svg?maxAge=300
|
9
|
-
[![
|
7
|
+
[![Build Status](https://github.com/aaronmallen/activeinteractor/workflows/Build/badge.svg)](https://github.com/aaronmallen/activeinteractor/actions)
|
8
|
+
[![Maintainability](https://img.shields.io/codeclimate/maintainability/aaronmallen/activeinteractor.svg?maxAge=300)](https://codeclimate.com/github/aaronmallen/activeinteractor/maintainability)
|
9
|
+
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/be92c4ecf12347da82d266f6a4368b6e)](https://www.codacy.com/manual/aaronmallen/activeinteractor?utm_source=github.com&utm_medium=referral&utm_content=aaronmallen/activeinteractor&utm_campaign=Badge_Grade)
|
10
|
+
[![Test Coverage](https://img.shields.io/codeclimate/coverage/aaronmallen/activeinteractor.svg?maxAge=300)](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).
|