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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -1
  3. data/README.md +397 -395
  4. data/lib/active_interactor.rb +12 -30
  5. data/lib/active_interactor/base.rb +18 -8
  6. data/lib/active_interactor/context.rb +4 -181
  7. data/lib/active_interactor/context/attributes.rb +37 -149
  8. data/lib/active_interactor/context/base.rb +141 -0
  9. data/lib/active_interactor/context/loader.rb +45 -0
  10. data/lib/active_interactor/error.rb +22 -15
  11. data/lib/active_interactor/interactor.rb +24 -57
  12. data/lib/active_interactor/interactor/callbacks.rb +64 -76
  13. data/lib/active_interactor/interactor/context.rb +97 -63
  14. data/lib/active_interactor/interactor/worker.rb +22 -65
  15. data/lib/active_interactor/organizer.rb +180 -164
  16. data/lib/active_interactor/version.rb +2 -3
  17. data/lib/rails/generators/active_interactor.rb +2 -37
  18. data/lib/rails/generators/active_interactor/application_interactor_generator.rb +23 -0
  19. data/lib/rails/generators/active_interactor/install_generator.rb +8 -12
  20. data/lib/rails/generators/active_interactor/templates/application_context.rb +4 -0
  21. data/lib/rails/generators/{templates/application_interactor.erb → active_interactor/templates/application_interactor.rb} +0 -0
  22. data/lib/rails/generators/active_interactor/templates/application_organizer.rb +4 -0
  23. data/lib/rails/generators/active_interactor/templates/initializer.erb +5 -0
  24. data/lib/rails/generators/interactor/context/rspec_generator.rb +19 -0
  25. data/lib/rails/generators/interactor/context/templates/rspec.erb +7 -0
  26. data/lib/rails/generators/interactor/context/templates/test_unit.erb +9 -0
  27. data/lib/rails/generators/interactor/context/test_unit_generator.rb +19 -0
  28. data/lib/rails/generators/interactor/context_generator.rb +19 -0
  29. data/lib/rails/generators/interactor/interactor_generator.rb +8 -3
  30. data/lib/rails/generators/interactor/organizer_generator.rb +8 -3
  31. data/lib/rails/generators/interactor/rspec_generator.rb +4 -3
  32. data/lib/rails/generators/interactor/templates/context.erb +4 -0
  33. data/lib/rails/generators/{templates → interactor/templates}/interactor.erb +0 -0
  34. data/lib/rails/generators/{templates → interactor/templates}/organizer.erb +1 -1
  35. data/lib/rails/generators/{templates → interactor/templates}/rspec.erb +0 -0
  36. data/lib/rails/generators/{templates → interactor/templates}/test_unit.erb +0 -0
  37. data/lib/rails/generators/interactor/test_unit_generator.rb +4 -3
  38. data/spec/active_interactor/base_spec.rb +51 -0
  39. data/spec/active_interactor/context/base_spec.rb +229 -0
  40. data/spec/active_interactor/error_spec.rb +43 -0
  41. data/spec/active_interactor/interactor/worker_spec.rb +89 -0
  42. data/spec/active_interactor/organizer_spec.rb +178 -0
  43. data/spec/active_interactor_spec.rb +26 -0
  44. data/spec/integration/basic_callback_integration_spec.rb +355 -0
  45. data/spec/integration/basic_context_integration_spec.rb +73 -0
  46. data/spec/integration/basic_integration_spec.rb +220 -0
  47. data/spec/integration/basic_validations_integration_spec.rb +204 -0
  48. data/spec/spec_helper.rb +44 -0
  49. data/spec/support/helpers/factories.rb +41 -0
  50. data/spec/support/shared_examples/a_class_with_interactor_callback_methods_example.rb +99 -0
  51. data/spec/support/shared_examples/a_class_with_interactor_context_methods_example.rb +58 -0
  52. data/spec/support/shared_examples/a_class_with_interactor_methods_example.rb +21 -0
  53. data/spec/support/shared_examples/a_class_with_organizer_callback_methods_example.rb +39 -0
  54. data/spec/support/spec_helpers.rb +7 -0
  55. metadata +68 -138
  56. data/lib/active_interactor/configuration.rb +0 -38
  57. data/lib/active_interactor/interactor/execution.rb +0 -24
  58. data/lib/rails/generators/templates/initializer.erb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62ffd94a00a7a624dc305a07dfb9347ac55d9b863b6a6acaf87d74a653bea21f
4
- data.tar.gz: bf704329e8360caaa6c750fb65722e8bf3cd53c0c633d7d1ed96f1b68fd4af0b
3
+ metadata.gz: f04cfc71e44fdbf89253cb31c1252781a094fa107763c0d27aa853f9d008774e
4
+ data.tar.gz: 2ca6bff9224913bd7420f74387c4d7cec2b704ac45f0d79ab5a4fa64ad46894b
5
5
  SHA512:
6
- metadata.gz: 22600e40ed597c10695c5bc1859f7338495c25b2204d9d58f959be3c74cb1e943f98776e0669dfc12e784eaf16babab7fc0531a98e15fbf2e6a0bbd42710d693
7
- data.tar.gz: f61f0ad8378ac49577e19c745566a202e5723cd107f7e867d0ed34249926c6827e258f81e42a7c617546e3e53448f106aba3a3754b1171fbb2d5c2f303217119
6
+ metadata.gz: 0d106d19d463424f0a406183713dc18ed27d6830fce6b685a96264a6f4396fd9ea18847c819b8dff12639cb319a42bb9ea22a5e5fbfade7e0678fe489a9fe25c
7
+ data.tar.gz: 75e3ef76a19f93ca5f795572403ad10338ef42c23db7e97b582fbe1922db8eafa946da83e02c5b20c129e415feb67b4e7c135c89151fd45a3f415c00e4fd0bf0
@@ -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/v0.1.7..HEAD
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&style=for-the-badge)](https://rubygems.org/gems/activeinteractor)
4
- [![License](https://img.shields.io/github/license/aaronmallen/activeinteractor.svg?maxAge=300&style=for-the-badge)](https://github.com/aaronmallen/activeinteractor/blob/master/LICENSE)
5
- [![Dependencies](https://img.shields.io/depfu/aaronmallen/activeinteractor.svg?maxAge=300&style=for-the-badge)](https://depfu.com/github/aaronmallen/activeinteractor)
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://img.shields.io/travis/com/aaronmallen/activeinteractor/master.svg?logo=travis&maxAge=300&style=for-the-badge)](https://www.travis-ci.com/aaronmallen/activeinteractor)
8
- [![Maintainability](https://img.shields.io/codeclimate/maintainability/aaronmallen/activeinteractor.svg?maxAge=300&style=for-the-badge)](https://codeclimate.com/github/aaronmallen/activeinteractor/maintainability)
9
- [![Test Coverage](https://img.shields.io/codeclimate/coverage/aaronmallen/activeinteractor.svg?maxAge=300&style=for-the-badge)](https://codeclimate.com/github/aaronmallen/activeinteractor/test_coverage)
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&amp;utm_medium=referral&amp;utm_content=aaronmallen/activeinteractor&amp;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 `context` and `context` class.
71
- For example:
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
- An interactor's context contains everything the interactor needs to do its work.
81
- When an interactor does its single purpose, it affects its given context.
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 `context` inherit from `OpenStruct`. As an interactor runs it can
86
- add information to it's `context`.
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
- context.user = user
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 a hash argument or an instance of `ActiveModel::Errors`, the fail!
101
- method can also update the context. The following are equivalent:
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.fail!
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
- context.failure? #=> false
116
- context.fail!
117
- context.failure? #=> true
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
- context.success? # => true
124
- context.fail!
125
- context.success? # => false
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
- object invokes the interactor using the class method call, then checks the `success?` method of
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 call class method swallows exceptions. When unit testing an interactor, if calling
137
- custom business logic methods directly and bypassing call, be aware that `fail!` will generate such exceptions.
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 `context` instance have basic attribute assignment methods which can be invoked directly
144
- from the interactor. You never need to directly interface with an interactor's context class.
145
- Assigning attributes to a `context` is a simple way to explicitly defined what properties a
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 `context` with the `#attributes` method:
193
+ You can see what attributes are defined on a given context with the `#attributes` method:
149
194
 
150
195
  ```ruby
151
- class MyInteractor < ActiveInteractor::Base
152
- # we define user as an attribute because it will be assigned a value
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
- context = MyInteractor.perform(
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
- #=> <#<MyInteractor::Context first_name='Aaron', last_name='Allen, email='hello@aaronmallen.me', occupation='Software Dude'>
208
+ #=> <#MyInteractor::Context first_name='Aaron' last_name='Allen' email='hello@aaronmallen.me' occupation='Software Dude'>
164
209
 
165
- context.attributes #=> { first_name: 'Aaron', last_name: 'Allen', email: 'hello@aaronmallen.me' }
166
- context.occupation #=> 'Software Dude'
210
+ result.attributes #=> { first_name: 'Aaron', last_name: 'Allen', email: 'hello@aaronmallen.me' }
211
+ result.occupation #=> 'Software Dude'
167
212
  ```
168
213
 
169
- You can see what properties are defined on a given `context` with the `#keys` method
170
- regardless of whether or not the properties are defined in a `context#attributes`:
214
+ #### Validating the Context
171
215
 
172
- ```ruby
173
- context.keys #=> [:first_name, :last_name, :email, :occupation]
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
- Finally you can invoke `#clean!` on a context to remove any properties not explicitly
177
- defined in a `context#attributes`:
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
- context.clean! #=> { occupation: 'Software Dude' }
181
- context.occupation #=> nil
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
- #### Aliasing Attributes
240
+ private
185
241
 
186
- Sometimes you may want to use the same interactor functionality with different
187
- model types having different naming conventions for similar attributes. We can
188
- inform the interactors context of these aliases with the `context_attribute_aliases`
189
- method on our interactors.
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
- context_attributes :first_name, :last_name
194
- context_attribute_aliases last_name: :sir_name
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
- context = MyInteractor.perform(first_name: 'Aaron', sir_name: 'Allen')
198
- # => <#MyInteractor::Context first_name='Aaron', last_name='Allen'>
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
- We can also pass an array of aliases to the attribute like this:
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 MyInteractor < ActiveInteractor::Base
205
- context_attributes :first_name, :last_name
206
- context_attribute_aliases last_name: %i[sir_name sirname]
207
- end
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
- context = MyInteractor.perform(first_name: 'Aaron', sir_name: 'Allen')
210
- # => <#MyInteractor::Context first_name='Aaron', last_name='Allen'>
287
+ private
211
288
 
212
- context = MyInteractor.perform(first_name: 'Aaron', sirname: 'Allen')
213
- # => <#MyInteractor::Context first_name='Aaron', last_name='Allen'>
289
+ def session_params
290
+ params.require(:session).permit(:email, :password)
291
+ end
292
+ end
214
293
  ```
215
294
 
216
- #### Validating the Context
295
+ can be refactored to:
296
+
297
+ ```ruby
298
+ class SessionsController < ApplicationController
299
+ def create
300
+ result = AuthenticateUser.perform(session_params)
217
301
 
218
- `ActiveInteractor` delegates all the validation methods provided by [ActiveModel::Validations]
219
- onto an interactor's context class from the interactor itself. All of the methods found in
220
- [ActiveModel::Validations] can be invoked directly on your interactor with the prefix `context_`.
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
- `ActiveInteractor` provides two validation callback steps:
311
+ private
223
312
 
224
- * `:calling` used before `#perform` is invoked
225
- * `:called` used after `#perform` is invoked
313
+ def session_params
314
+ params.require(:session).permit(:email, :password)
315
+ end
316
+ end
317
+ ```
226
318
 
227
- A basic implementation might look like this:
319
+ given the basic interactor and context:
228
320
 
229
321
  ```ruby
230
- class MyInteractor < ActiveInteractor::Base
231
- context_attributes :first_name, :last_name, :email, :user
232
- # only validates presence before perform is invoked
233
- context_validates :first_name, presence: true, on: :calling
234
- # validates before and after perform is invoked
235
- context_validates :email, presence: true,
236
- format: { with: URI::MailTo::EMAIL_REGEXP }
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.create_with(
243
- first_name: context.first_name,
244
- last_name: context.last_name
245
- ).find_or_create_by(email: context.email)
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
- private
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
- def user_is_a_user
251
- return if context.user.is_a?(User)
345
+ #### Kinds of Interactors
252
346
 
253
- context.errors.add(:user, :invalid)
254
- end
255
- end
347
+ There are two kinds of interactors built into the Interactor library: basic interactors and organizers.
256
348
 
257
- context = MyInteractor.perform(last_name: 'Allen')
258
- #=> <#MyInteractor::Context last_name='Allen>
259
- context.failure? #=> true
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
- context = MyInterator.perform(first_name: 'Aaron', email: 'hello@aaronmallen.me')
264
- #=> <#MyInteractor::Context first_name='Aaron', email='hello@aaronmallen.me'>
265
- context.success? #=> true
266
- context.valid? #=> true
267
- context.errors.empty? #=> true
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
- ### Callbacks
367
+ Basic interactors are the building blocks. They are your application's single-purpose units of work.
271
368
 
272
- `ActiveInteractor` uses [ActiveModel::Callbacks] and [ActiveModel::Validations::Callbacks]
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
- **NOTE:** When using symbolized method names as arguments the context class
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
- #### Validation Callbacks
373
+ ```ruby
374
+ class CreateOrder < ActiveInteractor::Base
375
+ def perform
376
+ ...
377
+ end
378
+ end
283
379
 
284
- We can do work before an interactor's context is validated with the `before_context_validation` method:
380
+ class ChargeCard < ActiveInteractor::Base
381
+ def perform
382
+ ...
383
+ end
384
+ end
285
385
 
286
- ```ruby
287
- class MyInteractor < ActiveInteractor::Base
288
- context_attributes :first_name, :last_name, :email, :user
289
- context_validates :last_name, presence: true
290
- before_context_validation { last_name ||= 'Unknown' }
386
+ class SendThankYou < ActiveInteractor::Base
387
+ def perform
388
+ ...
389
+ end
291
390
  end
292
391
 
293
- context = MyInteractor.perform(first_name: 'Aaron', email: 'hello@aaronmallen.me')
294
- context.valid? #=> true
295
- context.last_name #=> 'Unknown'
392
+ class PlaceOrder < ActiveInteractor::Organizer
393
+
394
+ organize :create_order, :charge_card, :send_thank_you
395
+ end
296
396
  ```
297
397
 
298
- We can do work after an interactor's context is validated with the `after_context_validation` method:
398
+ In the controller, you can run the `PlaceOrder` organizer just like you would any other interactor:
299
399
 
300
400
  ```ruby
301
- class MyInteractor < ActiveInteractor::Base
302
- context_attributes :first_name, :last_name, :email, :user
303
- context_validates :email, presence: true,
304
- format: { with: URI::MailTo::EMAIL_REGEXP }
305
- after_context_validation :downcase_email!
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 downcase_email
310
- context.email = context.email&.downcase!
415
+ def order_params
416
+ params.require(:order).permit!
311
417
  end
312
418
  end
419
+ ```
313
420
 
314
- context = MyInteractor.perform(first_name: 'Aaron', email: 'HELLO@aaronmallen.me')
315
- context.email #=> 'hello@aaronmallen.me'
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
- We can prevent a context from failing when invalid by invoking the
319
- `allow_context_to_be_invalid` class method:
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
- allow_context_to_be_invalid
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
- context = MyInteractor.perform(email: 'HELLO@aaronmallen.me')
329
- context.valid? #=> false
330
- context.success? #=> true
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
- #### Context Attribute Callbacks
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 MyInteractor < ActiveInteractor::Base
341
- clean_context_on_completion
342
- context_attributes :user
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
- def perform
345
- context.user = User.create_with(
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
- context = MyInteractor.perform(email: 'hello@aaronmallen.me', occupation: 'Software Dude')
352
- context.email #=> nil
353
- context.occupation #=> nil
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
- #### Perform Callbacks
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
- context = MyInteractor.perform
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
- context_validates :first_name, presence: true
386
- around_perform :track_time, if: :context_valid?
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
- context = MyInteractor.perform(first_name: 'Aaron')
398
- context.start_time #=> 2019-01-01 00:00:00 UTC
399
- context.end_time # #=> 2019-01-01 00:00:01 UTC
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
- context = MyInteractor.perform
559
+ MyInteractor.perform
425
560
  "Performing"
426
561
  "Done"
562
+ #=> <#MyInteractor::Context...>
427
563
  ```
428
564
 
429
- #### Rollback Callbacks
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
- context = MyInteractor.perform
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
- context = MyInteractor.perform
470
- context.rollback!
471
- context.start_time #=> 2019-01-01 00:00:00 UTC
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
- context = MyInteractor.perform
493
- context.rollback!
643
+ MyInteractor.perform
494
644
  "Rolling Back"
495
645
  "Done"
646
+ #=> <#MyInteractor::Context...>
496
647
  ```
497
648
 
498
- #### Organizer Callbacks
649
+ ##### Organizer Callbacks
499
650
 
500
- We can do worker before `perform` is invoked on each interactor in an [Organizer](#organizers)
501
- with the `before_each_perform` method:
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
- class MyInteractor1 < ActiveInteractor::Base
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(name: 'Aaron')
679
+ MyOrganizer.perform
533
680
  "Start"
534
681
  "MyInteractor1"
535
682
  "Start"
536
683
  "MyInteractor2"
537
- #=> <MyOrganizer::Context name='Aaron'>
684
+ #=> <MyOrganizer::Context...>
538
685
  ```
539
686
 
540
- We can do worker around `perform` is invokation on each interactor in an [Organizer](#organizers)
541
- with the `around_each_perform` method:
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(name: 'Aaron')
575
- "2019-04-01 00:00:00 UTC"
719
+ MyOrganizer.perform
720
+ "2019-01-01 00:00:00 UTC"
576
721
  "MyInteractor1"
577
- "2019-04-01 00:00:01 UTC"
578
- "2019-04-01 00:00:02 UTC"
722
+ "2019-01-01 00:00:01 UTC"
723
+ "2019-01-01 00:00:01 UTC"
579
724
  "MyInteractor2"
580
- "2019-04-01 00:00:03 UTC"
581
- #=> <MyOrganizer::Context name='Aaron'>
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
- with the `after_each_perform` method:
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 "done"
753
+ puts "Done"
613
754
  end
614
755
  end
615
756
 
616
- MyOrganizer.perform(name: 'Aaron')
757
+ MyOrganizer.perform
617
758
  "MyInteractor1"
618
759
  "Done"
619
760
  "MyInteractor2"
620
761
  "Done"
621
- #=> <MyOrganizer::Context name='Aaron'>
762
+ #=> <MyOrganizer::Context...>
622
763
  ```
623
764
 
624
- ### Using Interactors
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
- private
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
- def session_params
643
- params.require(:session).permit(:email, :password)
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
- The `perform` class method is the proper way to invoke an interactor.
693
- The hash argument is converted to the interactor instance's context.
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
- There are two kinds of interactors built into the Interactor library: basic interactors and organizers.
701
-
702
- #### Interactors
777
+ You can then automatically generate interactors, organizers, and contexts with:
703
778
 
704
- A basic interactor is a class that includes Interactor and defines call.
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
- Basic interactors are the building blocks. They are your application's single-purpose units of work.
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
- In the controller, you can run the `PlaceOrder` organizer just like you would any other interactor:
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
- The organizer passes its context to the interactors that it organizes, one at a time and in order.
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).