ii_interactor 1.0.0
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/.github/workflows/ci.yml +42 -0
- data/.gitignore +9 -0
- data/.rspec +3 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +368 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/gemfiles/rails50.gemfile +6 -0
- data/gemfiles/rails51.gemfile +6 -0
- data/gemfiles/rails52.gemfile +6 -0
- data/gemfiles/rails60.gemfile +5 -0
- data/gemfiles/rails61.gemfile +5 -0
- data/ii_interactor.gemspec +27 -0
- data/lib/ii_interactor.rb +23 -0
- data/lib/ii_interactor/base.rb +76 -0
- data/lib/ii_interactor/callbacks.rb +26 -0
- data/lib/ii_interactor/config.rb +21 -0
- data/lib/ii_interactor/context.rb +36 -0
- data/lib/ii_interactor/errors.rb +6 -0
- data/lib/ii_interactor/interaction.rb +43 -0
- data/lib/ii_interactor/loader.rb +19 -0
- data/lib/ii_interactor/lookup.rb +66 -0
- data/lib/ii_interactor/lookups/base.rb +20 -0
- data/lib/ii_interactor/lookups/name.rb +20 -0
- data/lib/ii_interactor/lookups/object.rb +40 -0
- data/lib/ii_interactor/version.rb +5 -0
- metadata +154 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ca26ce09ce20cc37d4e35337c05a5f49abf260a648752efeb1ac0e887f85dd8c
|
4
|
+
data.tar.gz: 0b6f575ef339301f4501b5abd0104dc8a4ce083fba0fe9e0fae4f8cc09b0d023
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cc4b8a3cfa9b100299f91c17e04efa110c8c482dd9a2d1365003f962bf27fc3682822b5970453bbf9e6d19b6a4267639052aa7acdf23a9c38a24245cdabeb323
|
7
|
+
data.tar.gz: a882894b2f36b99e1aed993ae7bf888c8fc9261ec8f1c9f1a8f15ce39a67e374476dff2baa6c4a643e3d946a0fa7d8956aa91dcfcf26a1af4395cde0da466fd1
|
@@ -0,0 +1,42 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
test:
|
7
|
+
runs-on: ubuntu-18.04
|
8
|
+
strategy:
|
9
|
+
fail-fast: false
|
10
|
+
matrix:
|
11
|
+
ruby: [2.3, 2.4, 2.5, 2.6, 2.7, 3.0]
|
12
|
+
gemfile: ['rails50', 'rails51', 'rails52', 'rails60', 'rails61']
|
13
|
+
exclude:
|
14
|
+
- ruby: 2.3
|
15
|
+
gemfile: rails60
|
16
|
+
- ruby: 2.3
|
17
|
+
gemfile: rails61
|
18
|
+
- ruby: 2.4
|
19
|
+
gemfile: rails60
|
20
|
+
- ruby: 2.4
|
21
|
+
gemfile: rails61
|
22
|
+
- ruby: 3.0
|
23
|
+
gemfile: rails50
|
24
|
+
- ruby: 3.0
|
25
|
+
gemfile: rails51
|
26
|
+
- ruby: 3.0
|
27
|
+
gemfile: rails52
|
28
|
+
|
29
|
+
name: ruby ${{ matrix.ruby }}, ${{ matrix.gemfile }}
|
30
|
+
|
31
|
+
env:
|
32
|
+
BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile
|
33
|
+
|
34
|
+
steps:
|
35
|
+
- uses: actions/checkout@v2
|
36
|
+
- uses: ruby/setup-ruby@v1
|
37
|
+
with:
|
38
|
+
ruby-version: ${{ matrix.ruby }}
|
39
|
+
bundler-cache: true
|
40
|
+
- name: Run test
|
41
|
+
run: |
|
42
|
+
bundle exec rspec
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Yoshikazu Kaneta
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,368 @@
|
|
1
|
+
# IIInteractor
|
2
|
+
|
3
|
+
A base interactor to support management of bussiness logic.
|
4
|
+
|
5
|
+
This gem is inspired by [interactor](https://github.com/collectiveidea/interactor) specs.
|
6
|
+
|
7
|
+
## Dependencies
|
8
|
+
|
9
|
+
* ruby 2.3+
|
10
|
+
* activesupport 5.0+
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'ii_interactor'
|
18
|
+
```
|
19
|
+
|
20
|
+
Then execute:
|
21
|
+
|
22
|
+
$ bundle
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Create interactor with `call` method and call it as follows:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
class Interactor < IIInteractor::Base
|
30
|
+
def call
|
31
|
+
@context.result = "called by #{@context.message}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Interactor.call(message: 'something')
|
36
|
+
#=> #<IIInteractor::Context message="something", result="called by something">
|
37
|
+
```
|
38
|
+
|
39
|
+
The first argument of `call` is set to `@context`.
|
40
|
+
The return value of `call` is the same as `@context`.
|
41
|
+
|
42
|
+
### Callbacks
|
43
|
+
|
44
|
+
Following callbacks are available:
|
45
|
+
|
46
|
+
* `before_call`
|
47
|
+
* `around_call`
|
48
|
+
* `after_call`
|
49
|
+
|
50
|
+
For example:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
class Interactor < IIInteractor::Base
|
54
|
+
before_call do
|
55
|
+
@message = @context.message
|
56
|
+
end
|
57
|
+
|
58
|
+
def call
|
59
|
+
puts @message
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
Interactor.call(message: 'something')
|
64
|
+
#=> something
|
65
|
+
```
|
66
|
+
|
67
|
+
### Interactions
|
68
|
+
|
69
|
+
You can call other interactors in the same context using `interact`:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
class AInteractor < IIInteractor::Base
|
73
|
+
def call
|
74
|
+
puts self.class.name
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class BInteractor < IIInteractor::Base
|
79
|
+
def call
|
80
|
+
puts self.class.name
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class MainInteractor < IIInteractor::Base
|
85
|
+
interact AInteractor
|
86
|
+
interact BInteractor
|
87
|
+
end
|
88
|
+
|
89
|
+
MainInteractor.call
|
90
|
+
#=> AInteractor
|
91
|
+
# BInteractor
|
92
|
+
```
|
93
|
+
|
94
|
+
#### Named interaction
|
95
|
+
|
96
|
+
You can also define named interactions.
|
97
|
+
The interactors to be called are looked up from all interactors.
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
class AInteractor < IIInteractor::Base
|
101
|
+
react :some_name
|
102
|
+
|
103
|
+
def call
|
104
|
+
puts self.class.name
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class BInteractor < IIInteractor::Base
|
109
|
+
react :some_name
|
110
|
+
|
111
|
+
def call
|
112
|
+
puts self.class.name
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class MainInteractor < IIInteractor::Base
|
117
|
+
interact :some_name
|
118
|
+
end
|
119
|
+
|
120
|
+
MainInteractor.call
|
121
|
+
#=> AInteractor
|
122
|
+
# BInteractor
|
123
|
+
```
|
124
|
+
|
125
|
+
Note followings:
|
126
|
+
|
127
|
+
* All files in `app/interactors` are loaded in development mode to lookup interactors having same name.
|
128
|
+
* The called interactors are unordered.
|
129
|
+
|
130
|
+
#### Object based interaction
|
131
|
+
|
132
|
+
You can also define object based interactions.
|
133
|
+
The interactors to be called are looked up from the namespace corresponding with caller interactor.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
class A
|
137
|
+
end
|
138
|
+
|
139
|
+
class B
|
140
|
+
end
|
141
|
+
|
142
|
+
class Main::AInteractor < IIInteractor::Base
|
143
|
+
def call
|
144
|
+
puts self.class.name
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class Main::BInteractor < IIInteractor::Base
|
149
|
+
def call
|
150
|
+
puts self.class.name
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class MainInteractor < IIInteractor::Base
|
155
|
+
interact A
|
156
|
+
interact B
|
157
|
+
end
|
158
|
+
|
159
|
+
MainInteractor.call
|
160
|
+
#=> Main::AInteractor
|
161
|
+
# Main::BInteractor
|
162
|
+
```
|
163
|
+
|
164
|
+
#### Custom interaction
|
165
|
+
|
166
|
+
You can also customize lookup of interactors as follows:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
class AInteractor < IIInteractor::Base
|
170
|
+
def call
|
171
|
+
puts self.class.name
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class BInteractor < IIInteractor::Base
|
176
|
+
def call
|
177
|
+
puts self.class.name
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class MainInteractor < IIInteractor::Base
|
182
|
+
# set block
|
183
|
+
interact do
|
184
|
+
if @context.condition == 'A'
|
185
|
+
AInteractor
|
186
|
+
else
|
187
|
+
BInteractor
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# set method name
|
192
|
+
interact :conditional_interactors
|
193
|
+
|
194
|
+
def conditional_interactors
|
195
|
+
if @context.condition == 'A'
|
196
|
+
AInteractor
|
197
|
+
else
|
198
|
+
BInteractor
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
MainInteractor.call(condition: 'A')
|
204
|
+
#=> AInteractor
|
205
|
+
|
206
|
+
MainInteractor.call(condition: 'B')
|
207
|
+
#=> BInteractor
|
208
|
+
```
|
209
|
+
|
210
|
+
#### Nested interaction
|
211
|
+
|
212
|
+
You can define nested interactions as follows:
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
class NestedAInteractor < IIInteractor::Base
|
216
|
+
def call
|
217
|
+
puts self.class.name
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
class NestedBInteractor < IIInteractor::Base
|
222
|
+
def call
|
223
|
+
puts self.class.name
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class AInteractor < IIInteractor::Base
|
228
|
+
interact NestedAInteractor
|
229
|
+
|
230
|
+
def call
|
231
|
+
puts self.class.name
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
class BInteractor < IIInteractor::Base
|
236
|
+
interact NestedBInteractor
|
237
|
+
|
238
|
+
def call
|
239
|
+
puts self.class.name
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
class MainInteractor < IIInteractor::Base
|
244
|
+
interact AInteractor
|
245
|
+
interact BInteractor
|
246
|
+
end
|
247
|
+
|
248
|
+
MainInteractor.call
|
249
|
+
#=> NestedAInteractor
|
250
|
+
# AInteractor
|
251
|
+
# NestedBInteractor
|
252
|
+
# BInteractor
|
253
|
+
```
|
254
|
+
|
255
|
+
### Stop interactions
|
256
|
+
|
257
|
+
You can stop interactions as follows:
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
class AInteractor < IIInteractor::Base
|
261
|
+
def call
|
262
|
+
puts self.class.name
|
263
|
+
stop!(message: "something happened!")
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
class BInteractor < IIInteractor::Base
|
268
|
+
def call
|
269
|
+
puts self.class.name
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
class MainInteractor < IIInteractor::Base
|
274
|
+
interact AInteractor
|
275
|
+
interact BInteractor
|
276
|
+
end
|
277
|
+
|
278
|
+
context = MainInteractor.call
|
279
|
+
#=> AInteractor
|
280
|
+
|
281
|
+
context.message
|
282
|
+
#=> something happened!
|
283
|
+
|
284
|
+
context.stopped?
|
285
|
+
#=> true
|
286
|
+
|
287
|
+
context.success?
|
288
|
+
#=> true
|
289
|
+
```
|
290
|
+
|
291
|
+
### Fail interactions
|
292
|
+
|
293
|
+
You can fail interactions and rollback called interactors as follows:
|
294
|
+
|
295
|
+
```ruby
|
296
|
+
class AInteractor < IIInteractor::Base
|
297
|
+
def call
|
298
|
+
puts self.class.name
|
299
|
+
end
|
300
|
+
|
301
|
+
def rollback
|
302
|
+
puts "rollback #{self.class.name}"
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
class BInteractor < IIInteractor::Base
|
307
|
+
def call
|
308
|
+
fail!(message: "something happened!")
|
309
|
+
end
|
310
|
+
|
311
|
+
def rollback
|
312
|
+
puts "rollback #{self.class.name}"
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
class MainInteractor < IIInteractor::Base
|
317
|
+
interact AInteractor
|
318
|
+
interact BInteractor
|
319
|
+
end
|
320
|
+
|
321
|
+
context = MainInteractor.call
|
322
|
+
#=> AInteractor
|
323
|
+
# rollback AInteractor
|
324
|
+
|
325
|
+
context.message
|
326
|
+
#=> something happened!
|
327
|
+
|
328
|
+
context.failure?
|
329
|
+
#=> true
|
330
|
+
```
|
331
|
+
|
332
|
+
### Pass a block
|
333
|
+
|
334
|
+
You can pass a block to `call` method of a interactor.
|
335
|
+
The block is kept in the context and you can call it by `inform` as you like:
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
class AInteractor < IIInteractor::Base
|
339
|
+
def call
|
340
|
+
inform('called A')
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
class BInteractor < IIInteractor::Base
|
345
|
+
def call
|
346
|
+
inform('called B')
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
class MainInteractor < IIInteractor::Base
|
351
|
+
interact AInteractor
|
352
|
+
interact BInteractor
|
353
|
+
end
|
354
|
+
|
355
|
+
MainInteractor.call do |interactor, message|
|
356
|
+
puts "#{interactor.class}: #{message}"
|
357
|
+
end
|
358
|
+
#=> AInteractor: called A
|
359
|
+
# BInteractor: called B
|
360
|
+
```
|
361
|
+
|
362
|
+
## Contributing
|
363
|
+
|
364
|
+
Bug reports and pull requests are welcome at https://github.com/kanety/ii_interactor.
|
365
|
+
|
366
|
+
## License
|
367
|
+
|
368
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "ii_interactor"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
# require "irb"
|
14
|
+
# IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ii_interactor/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ii_interactor"
|
8
|
+
spec.version = IIInteractor::VERSION
|
9
|
+
spec.authors = ["Yoshikazu Kaneta"]
|
10
|
+
spec.email = ["kaneta@sitebridge.co.jp"]
|
11
|
+
spec.summary = %q{A base interactor to support management of bussiness logic}
|
12
|
+
spec.description = %q{A base interactor to support management of bussiness logic}
|
13
|
+
spec.homepage = "https://github.com/kanety/ii_interactor"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
16
|
+
spec.bindir = "exe"
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency "activesupport", ">= 5.0"
|
21
|
+
|
22
|
+
spec.add_development_dependency "rails", ">= 5.0"
|
23
|
+
spec.add_development_dependency "sqlite3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec-rails"
|
26
|
+
spec.add_development_dependency "simplecov"
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
require 'ii_interactor/version'
|
4
|
+
require 'ii_interactor/errors'
|
5
|
+
require 'ii_interactor/config'
|
6
|
+
require 'ii_interactor/base'
|
7
|
+
require 'ii_interactor/loader'
|
8
|
+
|
9
|
+
module IIInteractor
|
10
|
+
class << self
|
11
|
+
def configure
|
12
|
+
yield Config
|
13
|
+
end
|
14
|
+
|
15
|
+
def config
|
16
|
+
Config
|
17
|
+
end
|
18
|
+
|
19
|
+
def load
|
20
|
+
Loader.call
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'context'
|
4
|
+
require_relative 'callbacks'
|
5
|
+
require_relative 'interaction'
|
6
|
+
require_relative 'lookup'
|
7
|
+
|
8
|
+
module IIInteractor
|
9
|
+
class Base
|
10
|
+
include Callbacks
|
11
|
+
include Interaction
|
12
|
+
include Lookup
|
13
|
+
|
14
|
+
attr_reader :context
|
15
|
+
|
16
|
+
def initialize(context = {}, &block)
|
17
|
+
@context = if context.is_a?(IIInteractor::Context)
|
18
|
+
context
|
19
|
+
else
|
20
|
+
IIInteractor::Context.new(context, &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def call_all
|
25
|
+
planned = lookup.map { |interactor| interactor.new(@context) } + [self]
|
26
|
+
@context._planned += planned
|
27
|
+
planned.each_with_index do |interactor, i|
|
28
|
+
if i == planned.size - 1
|
29
|
+
interactor.call_self
|
30
|
+
else
|
31
|
+
interactor.call_all
|
32
|
+
end
|
33
|
+
break if @context.stopped?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def call_self
|
38
|
+
run_callbacks :call do
|
39
|
+
call
|
40
|
+
end
|
41
|
+
@context._called << self
|
42
|
+
end
|
43
|
+
|
44
|
+
def call
|
45
|
+
end
|
46
|
+
|
47
|
+
def rollback
|
48
|
+
end
|
49
|
+
|
50
|
+
def inform(*args)
|
51
|
+
@context._block.call(*([self] + args)) if @context._block
|
52
|
+
end
|
53
|
+
|
54
|
+
def fail!(data = {})
|
55
|
+
@context.fail!(data)
|
56
|
+
raise UnprogressableError.new(@context)
|
57
|
+
end
|
58
|
+
|
59
|
+
def stop!(data = {})
|
60
|
+
@context.stop!(data)
|
61
|
+
end
|
62
|
+
|
63
|
+
class << self
|
64
|
+
def call(context = {}, &block)
|
65
|
+
interactor = new(context, &block)
|
66
|
+
interactor.call_all
|
67
|
+
interactor.context
|
68
|
+
rescue UnprogressableError
|
69
|
+
interactor.context._called.reverse.each do |called|
|
70
|
+
called.rollback
|
71
|
+
end
|
72
|
+
interactor.context
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IIInteractor
|
4
|
+
module Callbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include ActiveSupport::Callbacks
|
7
|
+
|
8
|
+
included do
|
9
|
+
define_callbacks :call
|
10
|
+
end
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
def before_call(*args, &block)
|
14
|
+
set_callback(:call, :before, *args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def after_call(*args, &block)
|
18
|
+
set_callback(:call, :after, *args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def around_call(*args, &block)
|
22
|
+
set_callback(:call, :around, *args, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IIInteractor
|
4
|
+
class Config
|
5
|
+
class_attribute :data
|
6
|
+
|
7
|
+
self.data = {
|
8
|
+
lookup_cache: true
|
9
|
+
}
|
10
|
+
|
11
|
+
data.keys.each do |key|
|
12
|
+
define_singleton_method "#{key}" do
|
13
|
+
data[key]
|
14
|
+
end
|
15
|
+
|
16
|
+
define_singleton_method "#{key}=" do |val|
|
17
|
+
data[key] = val
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IIInteractor
|
4
|
+
class Context < OpenStruct
|
5
|
+
def initialize(hash, &block)
|
6
|
+
super
|
7
|
+
self[:_block] = block
|
8
|
+
self[:_failed] = false
|
9
|
+
self[:_stopped] = false
|
10
|
+
self[:_planned] = []
|
11
|
+
self[:_called] = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def success?
|
15
|
+
!failure?
|
16
|
+
end
|
17
|
+
|
18
|
+
def failure?
|
19
|
+
self[:_failed] == true
|
20
|
+
end
|
21
|
+
|
22
|
+
def stopped?
|
23
|
+
self[:_stopped] == true
|
24
|
+
end
|
25
|
+
|
26
|
+
def fail!(data = {})
|
27
|
+
self[:_failed] = true
|
28
|
+
data.each { |k, v| self[k] = v }
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop!(data = {})
|
32
|
+
self[:_stopped] = true
|
33
|
+
data.each { |k, v| self[k] = v }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IIInteractor
|
4
|
+
module Interaction
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :_interactions
|
9
|
+
self._interactions = []
|
10
|
+
class_attribute :_reactions
|
11
|
+
self._reactions = []
|
12
|
+
end
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
def interact(*interactors, **options, &block)
|
16
|
+
if block
|
17
|
+
self._interactions = _interactions + [block]
|
18
|
+
elsif options[:before]
|
19
|
+
index = self._interactions.index { |interaction| interaction == options[:before] }
|
20
|
+
self._interactions = self._interactions.insert(index, *interactors)
|
21
|
+
else
|
22
|
+
self._interactions = _interactions + interactors
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def interactions
|
27
|
+
self._interactions
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear_interactions
|
31
|
+
self._interactions = []
|
32
|
+
end
|
33
|
+
|
34
|
+
def react(*reactions)
|
35
|
+
self._reactions = _reactions + reactions
|
36
|
+
end
|
37
|
+
|
38
|
+
def reactions
|
39
|
+
self._reactions
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IIInteractor
|
4
|
+
module Loader
|
5
|
+
class << self
|
6
|
+
def call
|
7
|
+
return unless defined?(Rails)
|
8
|
+
return if Rails.env.production?
|
9
|
+
|
10
|
+
engines = [Rails] + Rails::Engine.subclasses.map(&:instance)
|
11
|
+
engines.each do |engine|
|
12
|
+
Dir["#{engine.root}/app/interactors/**/*.rb"].each do |file|
|
13
|
+
require file
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lookups/base'
|
4
|
+
require_relative 'lookups/name'
|
5
|
+
require_relative 'lookups/object'
|
6
|
+
|
7
|
+
module IIInteractor
|
8
|
+
module Lookup
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
def lookup_all
|
12
|
+
lookup.map { |interactor| [interactor] + interactor.new(@context).lookup_all }.flatten
|
13
|
+
end
|
14
|
+
|
15
|
+
def lookup
|
16
|
+
self.class._interactions.map do |interaction|
|
17
|
+
if interaction.is_a?(Symbol) && respond_to?(interaction, true)
|
18
|
+
send(interaction)
|
19
|
+
elsif interaction.is_a?(Proc)
|
20
|
+
instance_exec(&interaction)
|
21
|
+
else
|
22
|
+
interaction
|
23
|
+
end
|
24
|
+
end.flatten.compact.map do |interaction|
|
25
|
+
if interaction.is_a?(Class) && interaction < IIInteractor::Base
|
26
|
+
interaction
|
27
|
+
else
|
28
|
+
self.class.lookup(interaction)
|
29
|
+
end
|
30
|
+
end.flatten.compact
|
31
|
+
end
|
32
|
+
|
33
|
+
class_methods do
|
34
|
+
def lookup(*interactions)
|
35
|
+
interactions = _interactions unless interactions
|
36
|
+
interactions.map { |interaction| Lookup.call(self, interaction) }.flatten
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class << self
|
41
|
+
class_attribute :lookups
|
42
|
+
self.lookups = [Lookups::Name, Lookups::Object]
|
43
|
+
|
44
|
+
class_attribute :_cache
|
45
|
+
self._cache = {}
|
46
|
+
|
47
|
+
def call(klass, interaction)
|
48
|
+
cache(klass, interaction) do
|
49
|
+
lookup = lookups.detect { |lookup| lookup.call?(interaction) }
|
50
|
+
lookup.new(klass, interaction).call if lookup
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def cache(klass, interaction)
|
57
|
+
if Config.lookup_cache
|
58
|
+
self._cache[klass] ||= {}
|
59
|
+
self._cache[klass][interaction] ||= yield
|
60
|
+
else
|
61
|
+
yield
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IIInteractor
|
4
|
+
module Lookups
|
5
|
+
class Base
|
6
|
+
def initialize(klass, interaction)
|
7
|
+
@klass = klass
|
8
|
+
@interaction = interaction
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def call?(interaction)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IIInteractor
|
4
|
+
module Lookups
|
5
|
+
class Name < Base
|
6
|
+
def call
|
7
|
+
IIInteractor.load
|
8
|
+
IIInteractor::Base.descendants.select do |interactor|
|
9
|
+
interactor._reactions.any? { |reaction| reaction == @interaction }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def call?(interaction)
|
15
|
+
interaction.is_a?(Symbol)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IIInteractor
|
4
|
+
module Lookups
|
5
|
+
class Object < Base
|
6
|
+
def call
|
7
|
+
return if terminate?
|
8
|
+
|
9
|
+
if @interaction.name.present? && (interactor = resolve)
|
10
|
+
interactor
|
11
|
+
elsif @interaction.superclass
|
12
|
+
self.class.new(@klass, @interaction.superclass).call
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def terminate?
|
19
|
+
@interaction.name.to_s.in?(['Object', 'ActiveRecord::Base', 'ActiveModel::Base'])
|
20
|
+
end
|
21
|
+
|
22
|
+
def resolve
|
23
|
+
name = resolve_name
|
24
|
+
interactor = name.safe_constantize
|
25
|
+
return interactor if interactor && name == interactor.name
|
26
|
+
end
|
27
|
+
|
28
|
+
def resolve_name
|
29
|
+
namespace = @klass.name.to_s.sub(/Interactor$/, '').to_s
|
30
|
+
[namespace, "#{@interaction.name}Interactor"].join('::')
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
def call?(interaction)
|
35
|
+
interaction.is_a?(Module)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ii_interactor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yoshikazu Kaneta
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-07-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sqlite3
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec-rails
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: simplecov
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: A base interactor to support management of bussiness logic
|
98
|
+
email:
|
99
|
+
- kaneta@sitebridge.co.jp
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".github/workflows/ci.yml"
|
105
|
+
- ".gitignore"
|
106
|
+
- ".rspec"
|
107
|
+
- Gemfile
|
108
|
+
- LICENSE
|
109
|
+
- README.md
|
110
|
+
- Rakefile
|
111
|
+
- bin/console
|
112
|
+
- bin/setup
|
113
|
+
- gemfiles/rails50.gemfile
|
114
|
+
- gemfiles/rails51.gemfile
|
115
|
+
- gemfiles/rails52.gemfile
|
116
|
+
- gemfiles/rails60.gemfile
|
117
|
+
- gemfiles/rails61.gemfile
|
118
|
+
- ii_interactor.gemspec
|
119
|
+
- lib/ii_interactor.rb
|
120
|
+
- lib/ii_interactor/base.rb
|
121
|
+
- lib/ii_interactor/callbacks.rb
|
122
|
+
- lib/ii_interactor/config.rb
|
123
|
+
- lib/ii_interactor/context.rb
|
124
|
+
- lib/ii_interactor/errors.rb
|
125
|
+
- lib/ii_interactor/interaction.rb
|
126
|
+
- lib/ii_interactor/loader.rb
|
127
|
+
- lib/ii_interactor/lookup.rb
|
128
|
+
- lib/ii_interactor/lookups/base.rb
|
129
|
+
- lib/ii_interactor/lookups/name.rb
|
130
|
+
- lib/ii_interactor/lookups/object.rb
|
131
|
+
- lib/ii_interactor/version.rb
|
132
|
+
homepage: https://github.com/kanety/ii_interactor
|
133
|
+
licenses: []
|
134
|
+
metadata: {}
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
requirements: []
|
150
|
+
rubygems_version: 3.1.2
|
151
|
+
signing_key:
|
152
|
+
specification_version: 4
|
153
|
+
summary: A base interactor to support management of bussiness logic
|
154
|
+
test_files: []
|