ii_interactor 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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