ii_interactor 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,9 @@
1
+ .bundle/
2
+ .project
3
+ Gemfile.lock
4
+ coverage/
5
+ pkg/
6
+ tmp/
7
+ spec/dummy/db/*.sqlite3
8
+ spec/dummy/db/schema_*.rb
9
+ spec/dummy/log/*.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "rails", "~> 5.0.0"
4
+ gem "sqlite3", "~> 1.3.6"
5
+
6
+ gemspec path: "../"
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "rails", "~> 5.1.0"
4
+ gem "sqlite3", "~> 1.3.6"
5
+
6
+ gemspec path: "../"
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "rails", "~> 5.2.0"
4
+ gem "sqlite3", "~> 1.3.6"
5
+
6
+ gemspec path: "../"
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "rails", "~> 6.0.0"
4
+
5
+ gemspec path: "../"
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "rails", "~> 6.1.0"
4
+
5
+ gemspec path: "../"
@@ -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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IIInteractor
4
+ class UnprogressableError < StandardError
5
+ end
6
+ 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IIInteractor
4
+ VERSION = '1.0.0'
5
+ 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: []