interactor_with_steroids 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.standard.yml +4 -0
- data/.travis.yml +37 -0
- data/CHANGELOG.md +50 -0
- data/CONTRIBUTING.md +49 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +602 -0
- data/Rakefile +7 -0
- data/interactor.gemspec +20 -0
- data/lib/interactor/context.rb +139 -0
- data/lib/interactor/declaration.rb +85 -0
- data/lib/interactor/error.rb +31 -0
- data/lib/interactor/hooks.rb +263 -0
- data/lib/interactor/organizer.rb +69 -0
- data/lib/interactor.rb +158 -0
- data/spec/interactor/context_spec.rb +141 -0
- data/spec/interactor/declaration_spec.rb +116 -0
- data/spec/interactor/hooks_spec.rb +358 -0
- data/spec/interactor/organizer_spec.rb +60 -0
- data/spec/interactor_spec.rb +127 -0
- data/spec/spec_helper.rb +6 -0
- metadata +113 -0
data/README.md
ADDED
@@ -0,0 +1,602 @@
|
|
1
|
+
# Interactor
|
2
|
+
|
3
|
+
[](http://rubygems.org/gems/interactor)
|
4
|
+
[](https://travis-ci.org/collectiveidea/interactor)
|
5
|
+
[](https://codeclimate.com/github/collectiveidea/interactor)
|
6
|
+
[](https://codeclimate.com/github/collectiveidea/interactor)
|
7
|
+
[](https://github.com/testdouble/standard)
|
8
|
+
|
9
|
+
## Getting Started
|
10
|
+
|
11
|
+
Add Interactor to your Gemfile and `bundle install`.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem "interactor", "~> 3.0"
|
15
|
+
```
|
16
|
+
|
17
|
+
## What is an Interactor?
|
18
|
+
|
19
|
+
An interactor is a simple, single-purpose object.
|
20
|
+
|
21
|
+
Interactors are used to encapsulate your application's
|
22
|
+
[business logic](http://en.wikipedia.org/wiki/Business_logic). Each interactor
|
23
|
+
represents one thing that your application *does*.
|
24
|
+
|
25
|
+
### Context
|
26
|
+
|
27
|
+
An interactor is given a *context*. The context contains everything the
|
28
|
+
interactor needs to do its work.
|
29
|
+
|
30
|
+
When an interactor does its single purpose, it affects its given context.
|
31
|
+
|
32
|
+
#### Adding to the Context
|
33
|
+
|
34
|
+
As an interactor runs it can add information to the context.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
context.user = user
|
38
|
+
```
|
39
|
+
|
40
|
+
#### Failing the Context
|
41
|
+
|
42
|
+
When something goes wrong in your interactor, you can flag the context as
|
43
|
+
failed.
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
context.fail!
|
47
|
+
```
|
48
|
+
|
49
|
+
When given a hash argument, the `fail!` method can also update the context. The
|
50
|
+
following are equivalent:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
context.error = "Boom!"
|
54
|
+
context.fail!
|
55
|
+
```
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
context.fail!(error: "Boom!")
|
59
|
+
```
|
60
|
+
|
61
|
+
You can ask a context if it's a failure:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
context.failure? # => false
|
65
|
+
context.fail!
|
66
|
+
context.failure? # => true
|
67
|
+
```
|
68
|
+
|
69
|
+
or if it's a success.
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
context.success? # => true
|
73
|
+
context.fail!
|
74
|
+
context.success? # => false
|
75
|
+
```
|
76
|
+
|
77
|
+
#### Dealing with Failure
|
78
|
+
|
79
|
+
`context.fail!` always throws an exception of type `Interactor::Failure`.
|
80
|
+
|
81
|
+
Normally, however, these exceptions are not seen. In the recommended usage, the controller invokes the interactor using the class method `call`, then checks the `success?` method of the context.
|
82
|
+
|
83
|
+
This works because the `call` class method swallows exceptions. When unit testing an interactor, if calling custom business logic methods directly and bypassing `call`, be aware that `fail!` will generate such exceptions.
|
84
|
+
|
85
|
+
See *Interactors in the Controller*, below, for the recommended usage of `call` and `success?`.
|
86
|
+
|
87
|
+
### Hooks
|
88
|
+
|
89
|
+
#### Before Hooks
|
90
|
+
|
91
|
+
Sometimes an interactor needs to prepare its context before the interactor is
|
92
|
+
even run. This can be done with before hooks on the interactor.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
before do
|
96
|
+
context.emails_sent = 0
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
A symbol argument can also be given, rather than a block.
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
before :zero_emails_sent
|
104
|
+
|
105
|
+
def zero_emails_sent
|
106
|
+
context.emails_sent = 0
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
#### After Hooks
|
111
|
+
|
112
|
+
Interactors can also perform teardown operations after the interactor instance
|
113
|
+
is run.
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
after do
|
117
|
+
context.user.reload
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
NB: After hooks are only run on success. If the `fail!` method is called, the interactor's after hooks are not run.
|
122
|
+
|
123
|
+
#### Around Hooks
|
124
|
+
|
125
|
+
You can also define around hooks in the same way as before or after hooks, using
|
126
|
+
either a block or a symbol method name. The difference is that an around block
|
127
|
+
or method accepts a single argument. Invoking the `call` method on that argument
|
128
|
+
will continue invocation of the interactor. For example, with a block:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
around do |interactor|
|
132
|
+
context.start_time = Time.now
|
133
|
+
interactor.call
|
134
|
+
context.finish_time = Time.now
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
With a method:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
around :time_execution
|
142
|
+
|
143
|
+
def time_execution(interactor)
|
144
|
+
context.start_time = Time.now
|
145
|
+
interactor.call
|
146
|
+
context.finish_time = Time.now
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
NB: If the `fail!` method is called, all of the interactor's around hooks cease execution, and no code after `interactor.call` will be run.
|
151
|
+
|
152
|
+
#### Hook Sequence
|
153
|
+
|
154
|
+
Before hooks are invoked in the order in which they were defined while after
|
155
|
+
hooks are invoked in the opposite order. Around hooks are invoked outside of any
|
156
|
+
defined before and after hooks. For example:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
around do |interactor|
|
160
|
+
puts "around before 1"
|
161
|
+
interactor.call
|
162
|
+
puts "around after 1"
|
163
|
+
end
|
164
|
+
|
165
|
+
around do |interactor|
|
166
|
+
puts "around before 2"
|
167
|
+
interactor.call
|
168
|
+
puts "around after 2"
|
169
|
+
end
|
170
|
+
|
171
|
+
before do
|
172
|
+
puts "before 1"
|
173
|
+
end
|
174
|
+
|
175
|
+
before do
|
176
|
+
puts "before 2"
|
177
|
+
end
|
178
|
+
|
179
|
+
after do
|
180
|
+
puts "after 1"
|
181
|
+
end
|
182
|
+
|
183
|
+
after do
|
184
|
+
puts "after 2"
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
will output:
|
189
|
+
|
190
|
+
```
|
191
|
+
around before 1
|
192
|
+
around before 2
|
193
|
+
before 1
|
194
|
+
before 2
|
195
|
+
after 2
|
196
|
+
after 1
|
197
|
+
around after 2
|
198
|
+
around after 1
|
199
|
+
```
|
200
|
+
|
201
|
+
#### Interactor Concerns
|
202
|
+
|
203
|
+
An interactor can define multiple before/after hooks, allowing common hooks to
|
204
|
+
be extracted into interactor concerns.
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
module InteractorTimer
|
208
|
+
extend ActiveSupport::Concern
|
209
|
+
|
210
|
+
included do
|
211
|
+
around do |interactor|
|
212
|
+
context.start_time = Time.now
|
213
|
+
interactor.call
|
214
|
+
context.finish_time = Time.now
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
### An Example Interactor
|
221
|
+
|
222
|
+
Your application could use an interactor to authenticate a user.
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
class AuthenticateUser
|
226
|
+
include Interactor
|
227
|
+
|
228
|
+
def call
|
229
|
+
if user = User.authenticate(context.email, context.password)
|
230
|
+
context.user = user
|
231
|
+
context.token = user.secret_token
|
232
|
+
else
|
233
|
+
context.fail!(message: "authenticate_user.failure")
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
```
|
238
|
+
|
239
|
+
To define an interactor, simply create a class that includes the `Interactor`
|
240
|
+
module and give it a `call` instance method. The interactor can access its
|
241
|
+
`context` from within `call`.
|
242
|
+
|
243
|
+
## Interactors in the Controller
|
244
|
+
|
245
|
+
Most of the time, your application will use its interactors from its
|
246
|
+
controllers. The following controller:
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
class SessionsController < ApplicationController
|
250
|
+
def create
|
251
|
+
if user = User.authenticate(session_params[:email], session_params[:password])
|
252
|
+
session[:user_token] = user.secret_token
|
253
|
+
redirect_to user
|
254
|
+
else
|
255
|
+
flash.now[:message] = "Please try again."
|
256
|
+
render :new
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
private
|
261
|
+
|
262
|
+
def session_params
|
263
|
+
params.require(:session).permit(:email, :password)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
```
|
267
|
+
|
268
|
+
can be refactored to:
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
class SessionsController < ApplicationController
|
272
|
+
def create
|
273
|
+
result = AuthenticateUser.call(session_params)
|
274
|
+
|
275
|
+
if result.success?
|
276
|
+
session[:user_token] = result.token
|
277
|
+
redirect_to result.user
|
278
|
+
else
|
279
|
+
flash.now[:message] = t(result.message)
|
280
|
+
render :new
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
private
|
285
|
+
|
286
|
+
def session_params
|
287
|
+
params.require(:session).permit(:email, :password)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
```
|
291
|
+
|
292
|
+
The `call` class method is the proper way to invoke an interactor. The hash
|
293
|
+
argument is converted to the interactor instance's context. The `call` instance
|
294
|
+
method is invoked along with any hooks that the interactor might define.
|
295
|
+
Finally, the context (along with any changes made to it) is returned.
|
296
|
+
|
297
|
+
## When to Use an Interactor
|
298
|
+
|
299
|
+
Given the user authentication example, your controller may look like:
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
class SessionsController < ApplicationController
|
303
|
+
def create
|
304
|
+
result = AuthenticateUser.call(session_params)
|
305
|
+
|
306
|
+
if result.success?
|
307
|
+
session[:user_token] = result.token
|
308
|
+
redirect_to result.user
|
309
|
+
else
|
310
|
+
flash.now[:message] = t(result.message)
|
311
|
+
render :new
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
private
|
316
|
+
|
317
|
+
def session_params
|
318
|
+
params.require(:session).permit(:email, :password)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
```
|
322
|
+
|
323
|
+
For such a simple use case, using an interactor can actually require *more*
|
324
|
+
code. So why use an interactor?
|
325
|
+
|
326
|
+
### Clarity
|
327
|
+
|
328
|
+
[We](http://collectiveidea.com) often use interactors right off the bat for all
|
329
|
+
of our destructive actions (`POST`, `PUT` and `DELETE` requests) and since we
|
330
|
+
put our interactors in `app/interactors`, a glance at that directory gives any
|
331
|
+
developer a quick understanding of everything the application *does*.
|
332
|
+
|
333
|
+
```
|
334
|
+
▾ app/
|
335
|
+
▸ controllers/
|
336
|
+
▸ helpers/
|
337
|
+
▾ interactors/
|
338
|
+
authenticate_user.rb
|
339
|
+
cancel_account.rb
|
340
|
+
publish_post.rb
|
341
|
+
register_user.rb
|
342
|
+
remove_post.rb
|
343
|
+
▸ mailers/
|
344
|
+
▸ models/
|
345
|
+
▸ views/
|
346
|
+
```
|
347
|
+
|
348
|
+
**TIP:** Name your interactors after your business logic, not your
|
349
|
+
implementation. `CancelAccount` will serve you better than `DestroyUser` as the
|
350
|
+
account cancellation interaction takes on more responsibility in the future.
|
351
|
+
|
352
|
+
### The Future™
|
353
|
+
|
354
|
+
**SPOILER ALERT:** Your use case won't *stay* so simple.
|
355
|
+
|
356
|
+
In [our](http://collectiveidea.com) experience, a simple task like
|
357
|
+
authenticating a user will eventually take on multiple responsibilities:
|
358
|
+
|
359
|
+
* Welcoming back a user who hadn't logged in for a while
|
360
|
+
* Prompting a user to update his or her password
|
361
|
+
* Locking out a user in the case of too many failed attempts
|
362
|
+
* Sending the lock-out email notification
|
363
|
+
|
364
|
+
The list goes on, and as that list grows, so does your controller. This is how
|
365
|
+
fat controllers are born.
|
366
|
+
|
367
|
+
If instead you use an interactor right away, as responsibilities are added, your
|
368
|
+
controller (and its tests) change very little or not at all. Choosing the right
|
369
|
+
kind of interactor can also prevent simply shifting those added responsibilities
|
370
|
+
to the interactor.
|
371
|
+
|
372
|
+
## Testing Interactors
|
373
|
+
|
374
|
+
When written correctly, an interactor is easy to test because it only *does* one
|
375
|
+
thing. Take the following interactor:
|
376
|
+
|
377
|
+
```ruby
|
378
|
+
class AuthenticateUser
|
379
|
+
include Interactor
|
380
|
+
|
381
|
+
def call
|
382
|
+
if user = User.authenticate(context.email, context.password)
|
383
|
+
context.user = user
|
384
|
+
context.token = user.secret_token
|
385
|
+
else
|
386
|
+
context.fail!(message: "authenticate_user.failure")
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
```
|
391
|
+
|
392
|
+
You can test just this interactor's single purpose and how it affects the
|
393
|
+
context.
|
394
|
+
|
395
|
+
```ruby
|
396
|
+
describe AuthenticateUser do
|
397
|
+
subject(:context) { AuthenticateUser.call(email: "john@example.com", password: "secret") }
|
398
|
+
|
399
|
+
describe ".call" do
|
400
|
+
context "when given valid credentials" do
|
401
|
+
let(:user) { double(:user, secret_token: "token") }
|
402
|
+
|
403
|
+
before do
|
404
|
+
allow(User).to receive(:authenticate).with("john@example.com", "secret").and_return(user)
|
405
|
+
end
|
406
|
+
|
407
|
+
it "succeeds" do
|
408
|
+
expect(context).to be_a_success
|
409
|
+
end
|
410
|
+
|
411
|
+
it "provides the user" do
|
412
|
+
expect(context.user).to eq(user)
|
413
|
+
end
|
414
|
+
|
415
|
+
it "provides the user's secret token" do
|
416
|
+
expect(context.token).to eq("token")
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
context "when given invalid credentials" do
|
421
|
+
before do
|
422
|
+
allow(User).to receive(:authenticate).with("john@example.com", "secret").and_return(nil)
|
423
|
+
end
|
424
|
+
|
425
|
+
it "fails" do
|
426
|
+
expect(context).to be_a_failure
|
427
|
+
end
|
428
|
+
|
429
|
+
it "provides a failure message" do
|
430
|
+
expect(context.message).to be_present
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
```
|
436
|
+
|
437
|
+
[We](http://collectiveidea.com) use RSpec but the same approach applies to any
|
438
|
+
testing framework.
|
439
|
+
|
440
|
+
### Isolation
|
441
|
+
|
442
|
+
You may notice that we stub `User.authenticate` in our test rather than creating
|
443
|
+
users in the database. That's because our purpose in
|
444
|
+
`spec/interactors/authenticate_user_spec.rb` is to test just the
|
445
|
+
`AuthenticateUser` interactor. The `User.authenticate` method is put through its
|
446
|
+
own paces in `spec/models/user_spec.rb`.
|
447
|
+
|
448
|
+
It's a good idea to define your own interfaces to your models. Doing so makes it
|
449
|
+
easy to draw a line between which responsibilities belong to the interactor and
|
450
|
+
which to the model. The `User.authenticate` method is a good, clear line.
|
451
|
+
Imagine the interactor otherwise:
|
452
|
+
|
453
|
+
```ruby
|
454
|
+
class AuthenticateUser
|
455
|
+
include Interactor
|
456
|
+
|
457
|
+
def call
|
458
|
+
user = User.where(email: context.email).first
|
459
|
+
|
460
|
+
# Yuck!
|
461
|
+
if user && BCrypt::Password.new(user.password_digest) == context.password
|
462
|
+
context.user = user
|
463
|
+
else
|
464
|
+
context.fail!(message: "authenticate_user.failure")
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
```
|
469
|
+
|
470
|
+
It would be very difficult to test this interactor in isolation and even if you
|
471
|
+
did, as soon as you change your ORM or your encryption algorithm (both model
|
472
|
+
concerns), your interactors (business concerns) break.
|
473
|
+
|
474
|
+
*Draw clear lines.*
|
475
|
+
|
476
|
+
### Integration
|
477
|
+
|
478
|
+
While it's important to test your interactors in isolation, it's just as
|
479
|
+
important to write good integration or acceptance tests.
|
480
|
+
|
481
|
+
One of the pitfalls of testing in isolation is that when you stub a method, you
|
482
|
+
could be hiding the fact that the method is broken, has changed or doesn't even
|
483
|
+
exist.
|
484
|
+
|
485
|
+
When you write full-stack tests that tie all of the pieces together, you can be
|
486
|
+
sure that your application's individual pieces are working together as expected.
|
487
|
+
That becomes even more important when you add a new layer to your code like
|
488
|
+
interactors.
|
489
|
+
|
490
|
+
**TIP:** If you track your test coverage, try for 100% coverage *before*
|
491
|
+
integrations tests. Then keep writing integration tests until you sleep well at
|
492
|
+
night.
|
493
|
+
|
494
|
+
### Controllers
|
495
|
+
|
496
|
+
One of the advantages of using interactors is how much they simplify controllers
|
497
|
+
and their tests. Because you're testing your interactors thoroughly in isolation
|
498
|
+
as well as in integration tests (right?), you can remove your business logic
|
499
|
+
from your controller tests.
|
500
|
+
|
501
|
+
```ruby
|
502
|
+
class SessionsController < ApplicationController
|
503
|
+
def create
|
504
|
+
result = AuthenticateUser.call(session_params)
|
505
|
+
|
506
|
+
if result.success?
|
507
|
+
session[:user_token] = result.token
|
508
|
+
redirect_to result.user
|
509
|
+
else
|
510
|
+
flash.now[:message] = t(result.message)
|
511
|
+
render :new
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
private
|
516
|
+
|
517
|
+
def session_params
|
518
|
+
params.require(:session).permit(:email, :password)
|
519
|
+
end
|
520
|
+
end
|
521
|
+
```
|
522
|
+
|
523
|
+
```ruby
|
524
|
+
describe SessionsController do
|
525
|
+
describe "#create" do
|
526
|
+
before do
|
527
|
+
expect(AuthenticateUser).to receive(:call).once.with(email: "john@doe.com", password: "secret").and_return(context)
|
528
|
+
end
|
529
|
+
|
530
|
+
context "when successful" do
|
531
|
+
let(:user) { double(:user, id: 1) }
|
532
|
+
let(:context) { double(:context, success?: true, user: user, token: "token") }
|
533
|
+
|
534
|
+
it "saves the user's secret token in the session" do
|
535
|
+
expect {
|
536
|
+
post :create, session: { email: "john@doe.com", password: "secret" }
|
537
|
+
}.to change {
|
538
|
+
session[:user_token]
|
539
|
+
}.from(nil).to("token")
|
540
|
+
end
|
541
|
+
|
542
|
+
it "redirects to the homepage" do
|
543
|
+
response = post :create, session: { email: "john@doe.com", password: "secret" }
|
544
|
+
|
545
|
+
expect(response).to redirect_to(user_path(user))
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
context "when unsuccessful" do
|
550
|
+
let(:context) { double(:context, success?: false, message: "message") }
|
551
|
+
|
552
|
+
it "sets a flash message" do
|
553
|
+
expect {
|
554
|
+
post :create, session: { email: "john@doe.com", password: "secret" }
|
555
|
+
}.to change {
|
556
|
+
flash[:message]
|
557
|
+
}.from(nil).to(I18n.translate("message"))
|
558
|
+
end
|
559
|
+
|
560
|
+
it "renders the login form" do
|
561
|
+
response = post :create, session: { email: "john@doe.com", password: "secret" }
|
562
|
+
|
563
|
+
expect(response).to render_template(:new)
|
564
|
+
end
|
565
|
+
end
|
566
|
+
end
|
567
|
+
end
|
568
|
+
```
|
569
|
+
|
570
|
+
This controller test will have to change very little during the life of the
|
571
|
+
application because all of the magic happens in the interactor.
|
572
|
+
|
573
|
+
### Rails
|
574
|
+
|
575
|
+
[We](http://collectiveidea.com) love Rails, and we use Interactor with Rails. We
|
576
|
+
put our interactors in `app/interactors` and we name them as verbs:
|
577
|
+
|
578
|
+
* `AddProductToCart`
|
579
|
+
* `AuthenticateUser`
|
580
|
+
* `PlaceOrder`
|
581
|
+
* `RegisterUser`
|
582
|
+
* `RemoveProductFromCart`
|
583
|
+
|
584
|
+
See: [Interactor Rails](https://github.com/collectiveidea/interactor-rails)
|
585
|
+
|
586
|
+
## Contributions
|
587
|
+
|
588
|
+
Interactor is open source and contributions from the community are encouraged!
|
589
|
+
No contribution is too small.
|
590
|
+
|
591
|
+
See Interactor's
|
592
|
+
[contribution guidelines](CONTRIBUTING.md) for more information.
|
593
|
+
|
594
|
+
## Thank You
|
595
|
+
|
596
|
+
A very special thank you to [Attila Domokos](https://github.com/adomokos) for
|
597
|
+
his fantastic work on [LightService](https://github.com/adomokos/light-service).
|
598
|
+
Interactor is inspired heavily by the concepts put to code by Attila.
|
599
|
+
|
600
|
+
Interactor was born from a desire for a slightly simplified interface. We
|
601
|
+
understand that this is a matter of personal preference, so please take a look
|
602
|
+
at LightService as well!
|
data/Rakefile
ADDED
data/interactor.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "English"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "interactor_with_steroids"
|
5
|
+
spec.version = "0.0.1"
|
6
|
+
|
7
|
+
spec.author = "Collective Idea/Sorare Team"
|
8
|
+
spec.email = "hello@sorare.com"
|
9
|
+
spec.description = "Interactor provides a common interface for performing complex user interactions."
|
10
|
+
spec.summary = "Simple interactor implementation"
|
11
|
+
spec.homepage = "https://github.com/sorare/interactor"
|
12
|
+
spec.license = "MIT"
|
13
|
+
|
14
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
15
|
+
spec.test_files = spec.files.grep(/^spec/)
|
16
|
+
|
17
|
+
spec.add_dependency "activesupport"
|
18
|
+
spec.add_development_dependency "bundler"
|
19
|
+
spec.add_development_dependency "rake"
|
20
|
+
end
|