ii_interactor 2.0.0 → 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2fdfb2e93eb53ee2b70845fb78c285aae26c237681537ae59893675873974f1
4
- data.tar.gz: b85095ca4396ccc3523fbe6f212ed335f057a77dc60e87b0969c14666a5e0d0f
3
+ metadata.gz: f27fe194dd295f5882f0533132588686a9fead26c638f86db95b8b66979553bf
4
+ data.tar.gz: '08c3c2fbd0687fb42077215abb8edd0e85a76eda538c418fa5f869f50957ed90'
5
5
  SHA512:
6
- metadata.gz: 2b1321aee1be7d0c362315ef1020db0113d77445366617de0e0f9ff010c49d1118f7a26f5182102065efa60eb832616ded910f1e3bd222fc897d5fcaa0947236
7
- data.tar.gz: a0d731eb5334b3846530e60858146c9b1f748d01b313917ee7e50153ab5f22f9fdb8a2cc53c24d08459ff7f074ee203579752c4d778bfad618e269c3361e318f
6
+ metadata.gz: ab6289235d21d6085ce7b20d123efd152d1e387f6d17a708a107e735f259036bb87be8533e9e7e5b07613cc9ca14fd0b699521bbb1c6f42c855054884f0e690e
7
+ data.tar.gz: 76c6d853fc4ef5b6103d9dda4cdac6ecb9179b0b21c6b35e72519dd2059bb1efb95a431ae23dca207f51a78e8a8e598c56028851842b98b6a4181221da725f76
@@ -4,21 +4,29 @@ on: [push, pull_request]
4
4
 
5
5
  jobs:
6
6
  test:
7
- runs-on: ubuntu-18.04
7
+ runs-on: ubuntu-20.04
8
8
  strategy:
9
9
  fail-fast: false
10
10
  matrix:
11
- ruby: [2.3, 2.4, 2.5, 2.6, 2.7, 3.0]
12
- gemfile: ['rails50', 'rails51', 'rails52', 'rails60', 'rails61']
11
+ ruby: [2.3, 2.4, 2.5, 2.6, 2.7, '3.0']
12
+ gemfile: ['rails50', 'rails51', 'rails52', 'rails60', 'rails61', 'rails70']
13
13
  exclude:
14
14
  - ruby: 2.3
15
15
  gemfile: rails60
16
16
  - ruby: 2.3
17
17
  gemfile: rails61
18
+ - ruby: 2.3
19
+ gemfile: rails70
18
20
  - ruby: 2.4
19
21
  gemfile: rails60
20
22
  - ruby: 2.4
21
23
  gemfile: rails61
24
+ - ruby: 2.4
25
+ gemfile: rails70
26
+ - ruby: 2.5
27
+ gemfile: rails70
28
+ - ruby: 2.6
29
+ gemfile: rails70
22
30
  - ruby: 3.0
23
31
  gemfile: rails50
24
32
  - ruby: 3.0
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  .project
3
3
  Gemfile.lock
4
4
  coverage/
5
+ gemfiles/*.lock
5
6
  pkg/
6
7
  tmp/
7
8
  spec/dummy/db/*.sqlite3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 2.1.0
4
+
5
+ * Add traversal config.
6
+ * Add callbacks for `call_all`.
7
+ * Add calling instrumentation.
8
+ * Bump coactive version to 0.2.
9
+
3
10
  ## 2.0.0
4
11
 
5
12
  * Replace interaction feature with coactive.
data/README.md CHANGED
@@ -27,6 +27,9 @@ Create interactor with `call` method and call it as follows:
27
27
 
28
28
  ```ruby
29
29
  class Interactor < IIInteractor::Base
30
+ context_in :message
31
+ context_out :result
32
+
30
33
  def call
31
34
  @context.result = "called by #{@context.message}"
32
35
  end
@@ -39,31 +42,13 @@ Interactor.call(message: 'something')
39
42
  The first argument of `Interactor.call` is set to `@context`.
40
43
  The return value of `Interactor.call` is the same as `@context`.
41
44
 
42
- ### Context variables
43
-
44
45
  You can define context variables used in interactor explicitly.
45
- For example:
46
-
47
- ```ruby
48
- class Interactor < IIInteractor::Base
49
- context_in :input
50
- context_out :result
51
-
52
- def call
53
- puts @input
54
- @result = 'result value'
55
- end
56
- end
57
-
58
- puts Interactor.call(input: 'input').result
59
- #=> input
60
- # result value
61
- ```
46
+ `context_in` copies context to instance variables of interactor,
47
+ while `context_out` copies instance variables of interactor to context.
62
48
 
63
- `context_in` copies context into instance variables of interactor,
64
- while `context_out` copies instance variables of interactor into context.
49
+ ### Context options
65
50
 
66
- You can also define required context as follows:
51
+ You can define required context as follows:
67
52
 
68
53
  ```ruby
69
54
  class Interactor < IIInteractor::Base
@@ -104,31 +89,6 @@ Interactor.call.result
104
89
  #=> returned value
105
90
  ```
106
91
 
107
- ### Callbacks
108
-
109
- Following callbacks are available:
110
-
111
- * `before_call`
112
- * `around_call`
113
- * `after_call`
114
-
115
- For example:
116
-
117
- ```ruby
118
- class Interactor < IIInteractor::Base
119
- before_call do
120
- @message = @context.message
121
- end
122
-
123
- def call
124
- puts @message
125
- end
126
- end
127
-
128
- Interactor.call(message: 'something')
129
- #=> something
130
- ```
131
-
132
92
  ### Coactions
133
93
 
134
94
  You can call other interactors in the same context using `coact`:
@@ -235,6 +195,38 @@ context.failure?
235
195
  #=> true
236
196
  ```
237
197
 
198
+ ### Callbacks
199
+
200
+ Following callbacks are available:
201
+
202
+ * `before_all`, `around_all`, `after_all`
203
+ * `before_call`, `around_call`, `after_call`
204
+
205
+ `*_all` wraps all coactors, and `*_call` wraps `call` method.
206
+ That is, `before_all` is called before running all coactors, and `before_call` is called before running `call` method.
207
+ For example:
208
+
209
+ ```ruby
210
+ class Interactor < IIInteractor::Base
211
+ before_all do
212
+ puts "before_all"
213
+ end
214
+
215
+ before_call do
216
+ puts "before_call"
217
+ end
218
+
219
+ def call
220
+ puts @context.message
221
+ end
222
+ end
223
+
224
+ Interactor.call(message: 'something')
225
+ #=> before_all
226
+ # before_call
227
+ # something
228
+ ```
229
+
238
230
  ### Pass a block
239
231
 
240
232
  You can pass a block to `call` method of a interactor.
@@ -277,7 +269,9 @@ IIInteractor::LogSubscriber.attach_to :ii_interactor
277
269
  This subscriber will write logs in debug mode as the following example:
278
270
 
279
271
  ```
280
- Called SimpleInteractor (Duration: 0.3ms, Allocations: 42)
272
+ Calling BasicInteractor with #<IIInteractor::Context ...>
273
+ ...
274
+ Called BasicInteractor (Duration: 0.1ms, Allocations: 4)
281
275
  ```
282
276
 
283
277
  ## Contributing
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "rails", "~> 7.0.0"
4
+
5
+ gemspec path: "../"
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.require_paths = ["lib"]
19
19
 
20
20
  spec.add_dependency "activesupport", ">= 5.0"
21
- spec.add_dependency "coactive", ">= 0.1"
21
+ spec.add_dependency "coactive", ">= 0.2"
22
22
 
23
23
  spec.add_development_dependency "rails", ">= 5.0"
24
24
  spec.add_development_dependency "sqlite3"
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'context'
4
3
  require_relative 'core'
5
- require_relative 'variables'
6
4
  require_relative 'callbacks'
7
5
  require_relative 'instrumentation'
6
+ require_relative 'context'
7
+ require_relative 'contextualizer'
8
8
  require_relative 'coactors'
9
9
 
10
10
  module IIInteractor
@@ -12,7 +12,7 @@ module IIInteractor
12
12
  include Core
13
13
  include Callbacks
14
14
  include Instrumentation
15
- include Variables
15
+ include Contextualizer
16
16
  include Coactors
17
17
  end
18
18
  end
@@ -6,9 +6,16 @@ module IIInteractor
6
6
  include ActiveSupport::Callbacks
7
7
 
8
8
  included do
9
+ define_callbacks :all
9
10
  define_callbacks :call
10
11
  end
11
12
 
13
+ def call_all
14
+ run_callbacks :all do
15
+ super
16
+ end
17
+ end
18
+
12
19
  def call_self
13
20
  run_callbacks :call do
14
21
  super
@@ -16,6 +23,18 @@ module IIInteractor
16
23
  end
17
24
 
18
25
  class_methods do
26
+ def before_all(*args, &block)
27
+ set_callback(:all, :before, *args, &block)
28
+ end
29
+
30
+ def after_all(*args, &block)
31
+ set_callback(:all, :after, *args, &block)
32
+ end
33
+
34
+ def around_all(*args, &block)
35
+ set_callback(:all, :around, *args, &block)
36
+ end
37
+
19
38
  def before_call(*args, &block)
20
39
  set_callback(:call, :before, *args, &block)
21
40
  end
@@ -5,6 +5,7 @@ module IIInteractor
5
5
  class_attribute :data
6
6
 
7
7
  self.data = {
8
+ traversal: :postorder,
8
9
  lookup_cache: true
9
10
  }
10
11
 
@@ -1,13 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IIInteractor
4
- class Context < OpenStruct
5
- def initialize(hash, &block)
4
+ class Context < Coactive::Context
5
+ def initialize(data = {}, &block)
6
6
  super
7
- self[:_block] = block
8
- self[:_failed] = false
9
- self[:_stopped] = false
10
- self[:_called] = []
7
+ @_data[:_block] ||= block
8
+ @_data[:_failed] ||= false
9
+ @_data[:_stopped] ||= false
10
+ @_data[:_called] ||= []
11
+ end
12
+
13
+ def to_s
14
+ attrs = @_data.reject { |k, _| k.to_s =~ /^_/ }.map { |k, v| "#{k}=#{v.to_s.truncate(300)}" }.join(', ')
15
+ "#<#{self.class} #{attrs}>"
16
+ end
17
+
18
+ def call_block!(*args)
19
+ @_data[:_block].call(*args) if @_data[:_block]
11
20
  end
12
21
 
13
22
  def success?
@@ -15,21 +24,27 @@ module IIInteractor
15
24
  end
16
25
 
17
26
  def failure?
18
- self[:_failed] == true
27
+ @_data[:_failed] == true
19
28
  end
20
29
 
21
30
  def stopped?
22
- self[:_stopped] == true
31
+ @_data[:_stopped] == true
23
32
  end
24
33
 
25
34
  def fail!(data = {})
26
- self[:_failed] = true
27
- data.each { |k, v| self[k] = v }
35
+ @_data[:_failed] = true
36
+ data.each { |k, v| @_data[k] = v }
37
+ define_accessors!(data.keys)
28
38
  end
29
39
 
30
40
  def stop!(data = {})
31
- self[:_stopped] = true
32
- data.each { |k, v| self[k] = v }
41
+ @_data[:_stopped] = true
42
+ data.each { |k, v| @_data[k] = v }
43
+ define_accessors!(data.keys)
44
+ end
45
+
46
+ def called!(interactor)
47
+ @_data[:_called] << interactor
33
48
  end
34
49
  end
35
50
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IIInteractor
4
+ module Contextualizer
5
+ extend ActiveSupport::Concern
6
+ include Coactive::Contextualizer
7
+
8
+ def call_self
9
+ contextualize do
10
+ super
11
+ end
12
+ end
13
+
14
+ class_methods do
15
+ def context_in(*names, **options)
16
+ context(*names, **options)
17
+ end
18
+
19
+ def context_out(*names, **options)
20
+ options[:output] = true
21
+ if options.delete(:from_return)
22
+ options[:output] = :return
23
+ end
24
+ context(*names, **options)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -3,26 +3,32 @@
3
3
  module IIInteractor
4
4
  module Core
5
5
  extend ActiveSupport::Concern
6
+ include Coactive::Initializer
6
7
 
7
8
  included do
8
- attr_reader :context
9
+ self.context_class = IIInteractor::Context
9
10
  end
10
11
 
11
- def initialize(context = {}, &block)
12
- @context = if context.is_a?(IIInteractor::Context)
13
- context
14
- else
15
- IIInteractor::Context.new(context, &block)
16
- end
12
+ def initialize(*)
13
+ super
17
14
  end
18
15
 
19
16
  def call_all
20
- planned = coactors.map { |interactor| interactor.new(@context) } + [self]
21
- planned.each_with_index do |interactor, i|
22
- if i == planned.size - 1
23
- interactor.call_self
17
+ planned = case IIInteractor.config.traversal
18
+ when :preorder
19
+ [self] + coactors
20
+ when :postorder
21
+ coactors + [self]
22
+ when :inorder
23
+ planned = coactors.in_groups(2, false)
24
+ planned[0] + [self] + planned[1]
25
+ end
26
+
27
+ planned.each do |interactor|
28
+ if interactor == self
29
+ call_self
24
30
  else
25
- interactor.call_all
31
+ interactor.new(@context).call_all
26
32
  end
27
33
  break if @context.stopped?
28
34
  end
@@ -30,7 +36,7 @@ module IIInteractor
30
36
 
31
37
  def call_self
32
38
  call.tap do
33
- @context._called << self
39
+ @context.called!(self)
34
40
  end
35
41
  end
36
42
 
@@ -41,7 +47,7 @@ module IIInteractor
41
47
  end
42
48
 
43
49
  def inform(*args)
44
- @context._block.call(*([self] + args)) if @context._block
50
+ @context.call_block!(*([self] + args))
45
51
  end
46
52
 
47
53
  def fail!(data = {})
@@ -54,12 +60,12 @@ module IIInteractor
54
60
  end
55
61
 
56
62
  class_methods do
57
- def call(*args, &block)
58
- interactor = new(*args, &block)
63
+ def call(args = {}, &block)
64
+ interactor = new(args, &block)
59
65
  interactor.call_all
60
66
  interactor.context
61
67
  rescue UnprogressableError
62
- interactor.context._called.reverse.each do |called|
68
+ interactor.context[:_called].reverse.each do |called|
63
69
  called.rollback
64
70
  end
65
71
  interactor.context
@@ -4,6 +4,11 @@ module IIInteractor
4
4
  module Instrumentation
5
5
  extend ActiveSupport::Concern
6
6
 
7
+ def call_all
8
+ ActiveSupport::Notifications.instrument 'calling.ii_interactor', interactor: self
9
+ super
10
+ end
11
+
7
12
  def call_self
8
13
  ActiveSupport::Notifications.instrument 'call.ii_interactor', interactor: self do
9
14
  super
@@ -2,6 +2,13 @@
2
2
 
3
3
  module IIInteractor
4
4
  class LogSubscriber < ActiveSupport::LogSubscriber
5
+ def calling(event)
6
+ debug do
7
+ interactor = event.payload[:interactor]
8
+ "Calling #{interactor.class} with #{interactor.context}"
9
+ end
10
+ end
11
+
5
12
  def call(event)
6
13
  debug do
7
14
  interactor = event.payload[:interactor]
@@ -9,6 +16,8 @@ module IIInteractor
9
16
  end
10
17
  end
11
18
 
19
+ private
20
+
12
21
  def additional_log(event)
13
22
  additions = ["Duration: %.1fms" % event.duration]
14
23
  additions << "Allocations: %d" % event.allocations if event.respond_to?(:allocations)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IIInteractor
4
- VERSION = '2.0.0'
4
+ VERSION = '2.1.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ii_interactor
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yoshikazu Kaneta
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-29 00:00:00.000000000 Z
11
+ date: 2022-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0.1'
33
+ version: '0.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0.1'
40
+ version: '0.2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rails
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -130,6 +130,7 @@ files:
130
130
  - gemfiles/rails52.gemfile
131
131
  - gemfiles/rails60.gemfile
132
132
  - gemfiles/rails61.gemfile
133
+ - gemfiles/rails70.gemfile
133
134
  - ii_interactor.gemspec
134
135
  - lib/ii_interactor.rb
135
136
  - lib/ii_interactor/base.rb
@@ -137,11 +138,11 @@ files:
137
138
  - lib/ii_interactor/coactors.rb
138
139
  - lib/ii_interactor/config.rb
139
140
  - lib/ii_interactor/context.rb
141
+ - lib/ii_interactor/contextualizer.rb
140
142
  - lib/ii_interactor/core.rb
141
143
  - lib/ii_interactor/errors.rb
142
144
  - lib/ii_interactor/instrumentation.rb
143
145
  - lib/ii_interactor/log_subscriber.rb
144
- - lib/ii_interactor/variables.rb
145
146
  - lib/ii_interactor/version.rb
146
147
  homepage: https://github.com/kanety/ii_interactor
147
148
  licenses: []
@@ -161,7 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
161
162
  - !ruby/object:Gem::Version
162
163
  version: '0'
163
164
  requirements: []
164
- rubygems_version: 3.0.3
165
+ rubygems_version: 3.1.6
165
166
  signing_key:
166
167
  specification_version: 4
167
168
  summary: A base interactor to support management of bussiness logic
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module IIInteractor
4
- class Variable < Struct.new(:name, :options)
5
- def default
6
- options[:default] if options
7
- end
8
-
9
- def required?
10
- options[:required] if options
11
- end
12
-
13
- def from_return?
14
- options[:from_return] if options
15
- end
16
- end
17
-
18
- module Variables
19
- extend ActiveSupport::Concern
20
-
21
- def call_self
22
- (self.class.context_ins + self.class.context_outs).each do |var|
23
- if var.required? && !@context.respond_to?(var.name)
24
- raise RequiredContextError.new("missing required context: #{var.name}")
25
- end
26
- if var.default && !@context.respond_to?(var.name)
27
- @context[var.name] = Variables.resolve(self, var.default)
28
- end
29
- instance_variable_set("@#{var.name}", @context[var.name])
30
- end
31
-
32
- super.tap do |return_value|
33
- self.class.context_outs.each do |var|
34
- @context[var.name] =
35
- if var.from_return?
36
- return_value
37
- else
38
- instance_variable_get("@#{var.name}")
39
- end
40
- end
41
- end
42
- end
43
-
44
- class << self
45
- def resolve(interactor, value)
46
- if value.respond_to?(:call)
47
- interactor.instance_exec(&value)
48
- elsif value.is_a?(Symbol) && interactor.respond_to?(value, true)
49
- interactor.send(value)
50
- else
51
- value.deep_dup
52
- end
53
- end
54
- end
55
-
56
- included do
57
- class_attribute :context_ins, :context_outs
58
- self.context_ins = []
59
- self.context_outs = []
60
- end
61
-
62
- class_methods do
63
- def context_in(*names, **options)
64
- self.context_ins = self.context_ins + names.map { |name| Variable.new(name, options) }
65
- end
66
-
67
- def context_out(*names, **options)
68
- self.context_outs = self.context_outs + names.map { |name| Variable.new(name, options) }
69
- end
70
- end
71
- end
72
- end