activeinteractor 0.1.7 → 1.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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).