flows 0.5.1 → 0.6.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: e6f029a3e03c0cd3ac484cb703f67a1c4910a10c8395e7b1024518dc84a14cb5
4
- data.tar.gz: 23f44992d336cd637c02ff6d96c82a99f7e6af2a2affc82bcdc2c3f4ee1153a2
3
+ metadata.gz: dd10b19c7b749fd46c6b8897db3c9912603b4111af7793449d5ff49f69adfcb1
4
+ data.tar.gz: b18271b7321f199e978ebd4f66f2d21e532c20dd37cd61b6b641b00d875c2f4c
5
5
  SHA512:
6
- metadata.gz: 17015a5eb9868ced9c18872706625df9aeb51aa90717be36929fe223006b5e28fa439fcc63db49b43a047c0375b3d6493f4640da6164be21815b3df95291ae96
7
- data.tar.gz: b2170ec9d8a710b70fd676772ba7a5556ab23b9a50313bf34f321afb440f07d9fe66bc76634a9e53f02dd7063ebf92e27a94e162e6470e2b22a87703a6856f48
6
+ metadata.gz: 6b3e90d9b76911f1e0a0539bd89eee76a68dcba04f4136858412e4d0e0e1ca51ff8f35027963ef52fdd870f2b74ffe9a326aca4802cd443af2e55f8bf18f67b1
7
+ data.tar.gz: 5a6306a15a6024a1c9412f325ab3454b029abcd7d80defee6a9d5b286e8664981b572fb0ecf6f91d06b98c7d5f3040169fe02aae43b8ee8261fa7b65c469fe54
@@ -1,10 +1,12 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
1
3
  require:
2
4
  - rubocop-rspec
3
5
  - rubocop-performance
4
6
  - rubocop-md
5
7
 
6
8
  AllCops:
7
- TargetRubyVersion: 2.7
9
+ TargetRubyVersion: 2.5
8
10
  NewCops: enable
9
11
 
10
12
  Style/HashEachMethods:
@@ -0,0 +1,27 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config --auto-gen-only-exclude`
3
+ # on 2020-08-22 07:49:05 UTC using RuboCop version 0.89.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 27
10
+ #
11
+ # *** NON-GENERATED COMMENT ***
12
+ # I'm not sure about enabling this cop
13
+ # * using super may has performance impact
14
+ # * but for some cases it still can be useful
15
+ # Research needed
16
+ Lint/MissingSuper:
17
+ Enabled: false
18
+
19
+ # Offense count: 5
20
+ # Configuration parameters: AllowSubject, Max.
21
+ #
22
+ # *** NON-GENERATED COMMENT ***
23
+ # Might be it's impossible to satisfy this cop or significant refactoring of these tests needed
24
+ RSpec/MultipleMemoizedHelpers:
25
+ Exclude:
26
+ - 'spec/flows/flow/node_spec.rb'
27
+ - 'spec/flows/util/inheritable_singleton_vars/isolation_strategy_spec.rb'
@@ -1 +1 @@
1
- 2.6.5
1
+ 2.6.6
@@ -16,6 +16,21 @@ Types of changes:
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [0.6.0] - 2020-09-23
20
+
21
+ ### Added
22
+
23
+ * `Flows::Plugin::Interface` basic implementation
24
+ * `Flows::SharedContextPipeline` simple sub-pipelines injection
25
+
26
+ ### Changed
27
+
28
+ * `Flows::SharedContextPipeline` callbacks execution method is changed to `instance_exec` (previously was `.call`)
29
+
30
+ ### Fixed
31
+
32
+ * `Flows::Plugin::DependencyInjector` and modules generated by `Flows::Util::InheritableSingletonVars::DupStrategy` now can be safely included several times in the inheritance chain
33
+
19
34
  ## [0.5.1] - 2020-06-29
20
35
 
21
36
  ### Fixed
@@ -59,7 +74,8 @@ Types of changes:
59
74
  * `Flows::Util::PrependToClass` - allows to prepend some module to class even if
60
75
  target module did not included directly into class.
61
76
 
62
- [unreleased]: https://github.com/ffloyd/flows/compare/v0.5.1...HEAD
77
+ [unreleased]: https://github.com/ffloyd/flows/compare/v0.6.0...HEAD
78
+ [0.6.0]: https://github.com/ffloyd/flows/compare/v0.5.1...v0.6.0
63
79
  [0.5.1]: https://github.com/ffloyd/flows/compare/v0.5.0...v0.5.1
64
80
  [0.5.0]: https://github.com/ffloyd/flows/compare/v0.4.0...v0.5.0
65
81
  [0.4.0]: https://github.com/ffloyd/flows/compare/v0.3.0...v0.4.0
@@ -17,3 +17,6 @@ Lint/UselessAssignment:
17
17
 
18
18
  Metrics/MethodLength:
19
19
  Enabled: false
20
+
21
+ Metrics/AbcSize:
22
+ Enabled: false
data/bin/errors CHANGED
@@ -17,6 +17,7 @@ require_relative 'errors_cli/railway_error_demo'
17
17
  require_relative 'errors_cli/result_error_demo'
18
18
  require_relative 'errors_cli/scp_error_demo'
19
19
  require_relative 'errors_cli/flow_error_demo'
20
+ require_relative 'errors_cli/interface_error_demo'
20
21
 
21
22
  class ErrorsCLI
22
23
  extend GLI::App
@@ -30,11 +31,11 @@ class ErrorsCLI
30
31
  ctx.command name do |cmd|
31
32
  cmd.action do |_, _, _|
32
33
  puts title.green
33
- puts(('BEGIN' + '-' * (title.length - 5)).color(:darkgray))
34
+ puts("BEGIN#{'-' * (title.length - 5)}".color(:darkgray))
34
35
  yield
35
36
  rescue StandardError => err
36
37
  puts err.message
37
- puts(('END' + '-' * (title.length - 3)).color(:darkgray))
38
+ puts("END#{'-' * (title.length - 3)}".color(:darkgray))
38
39
  puts
39
40
  end
40
41
  end
@@ -125,6 +126,13 @@ class ErrorsCLI
125
126
  FlowErrorDemo.invalid_node_route
126
127
  end
127
128
  end
129
+
130
+ desc 'Interface errors'
131
+ command :interface do |cmd|
132
+ make_cmd cmd, 'Missing Implementation', :missing_implementation do
133
+ InterfaceErrorDemo.missing_implementation
134
+ end
135
+ end
128
136
  end
129
137
 
130
138
  exit ErrorsCLI.run(ARGV)
@@ -0,0 +1,17 @@
1
+ module InterfaceErrorDemo
2
+ class Parent
3
+ extend Flows::Plugin::Interface
4
+
5
+ defmethod :execute
6
+ defmethod :debug
7
+ end
8
+
9
+ class Child < Parent
10
+ end
11
+
12
+ class << self
13
+ def missing_implementation
14
+ Child.new
15
+ end
16
+ end
17
+ end
@@ -21,6 +21,8 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ['lib']
23
23
 
24
+ spec.required_ruby_version = '>= 2.5'
25
+
24
26
  # This library has no production dependencies.
25
27
  # So, it will not block you from updating any dependencies in your project.
26
28
  # So, don't add production dependencies.
@@ -394,9 +394,9 @@ module Flows
394
394
 
395
395
  # :reek:UtilityFunction
396
396
  def merge_nested_errors(description, nested_errors)
397
- shifted = nested_errors.split("\n").map { |str| ' ' + str }.join("\n")
397
+ shifted = nested_errors.split("\n").map { |str| " #{str}" }.join("\n")
398
398
 
399
- description + "\n" + shifted
399
+ "#{description}\n#{shifted}"
400
400
  end
401
401
  end
402
402
  end
@@ -2,8 +2,7 @@ module Flows
2
2
  class Contract
3
3
  # Class for {Type} errors.
4
4
  class Error < ::Flows::Error
5
- attr_reader :value
6
- attr_reader :value_error
5
+ attr_reader :value, :value_error
7
6
 
8
7
  # @param value [Object] checked value
9
8
  # @param value_error [String] error message
@@ -12,3 +12,4 @@ require_relative 'plugin/dependency_injector'
12
12
  require_relative 'plugin/implicit_init'
13
13
  require_relative 'plugin/output_contract'
14
14
  require_relative 'plugin/profiler'
15
+ require_relative 'plugin/interface'
@@ -136,6 +136,17 @@ module Flows
136
136
 
137
137
  InitializerWrapper = Util::PrependToClass.make_module do
138
138
  def initialize(*args, **kwargs, &block) # rubocop:disable Metrics/MethodLength
139
+ if @__dependencies_injected__
140
+ if kwargs.empty? # https://bugs.ruby-lang.org/issues/14415
141
+ super(*args, &block)
142
+ else
143
+ super(*args, **kwargs, &block)
144
+ end
145
+
146
+ return
147
+ end
148
+ @__dependencies_injected__ = true
149
+
139
150
  klass = self.class
140
151
  DependencyList.new(
141
152
  klass: klass,
@@ -5,9 +5,7 @@ module Flows
5
5
  #
6
6
  # @api private
7
7
  class DependencyList
8
- attr_reader :definitions
9
- attr_reader :provided_values
10
- attr_reader :dependencies
8
+ attr_reader :definitions, :provided_values, :dependencies
11
9
 
12
10
  def initialize(klass:, definitions:, provided_values:)
13
11
  @klass = klass
@@ -0,0 +1,84 @@
1
+ module Flows
2
+ module Plugin
3
+ # Class extension to define Java/C#-like interfaces in Ruby.
4
+ #
5
+ # On target class initialization will check defined methods for existence.
6
+ #
7
+ # **Currently interface composition is not supported.** You cannot define
8
+ # 2 interface modules and include it into one class.
9
+ #
10
+ # @example Simple interface
11
+ # class MyAction
12
+ # extend Flows::Plugin::Interface
13
+ #
14
+ # defmethod :perform
15
+ # end
16
+ #
17
+ # class InvalidAction < MyAction; end
18
+ # InvalidAction.new
19
+ # # will raise an error
20
+ #
21
+ # class ValidAction < MyAction
22
+ # def perfrom
23
+ # puts 'Hello!'
24
+ # end
25
+ # end
26
+ # ValidAction.new.perform
27
+ # # => Hello!
28
+ #
29
+ # @example Interface as module
30
+ # module MyBehavior
31
+ # extend Flows::Plugin::Interface
32
+ #
33
+ # defmethod :my_method
34
+ # end
35
+ #
36
+ # class MyImplementation
37
+ # include MyBehaviour
38
+ #
39
+ # def my_method; end
40
+ # end
41
+ module Interface
42
+ # Base error class for interface errors.
43
+ class Error < ::Flows::Error; end
44
+
45
+ # Raised when you're missed some dependency.
46
+ class MissingMethodsError < Error
47
+ def initialize(klass, names)
48
+ @klass = klass
49
+ @names = names
50
+ end
51
+
52
+ def message
53
+ "Methods required by interface for #{@klass} are missing: #{@names.map(&:to_s).join(', ')}"
54
+ end
55
+ end
56
+
57
+ SingletonVarsSetup = Flows::Util::InheritableSingletonVars::DupStrategy.make_module(
58
+ '@interface_methods' => {}
59
+ )
60
+
61
+ include SingletonVarsSetup
62
+
63
+ InitializePatch = Flows::Util::PrependToClass.make_module do
64
+ def initialize(*)
65
+ klass = self.class
66
+
67
+ required_methods = klass.instance_variable_get(:@interface_methods).keys
68
+ missing_methods = required_methods - methods
69
+
70
+ raise MissingMethodsError.new(klass, missing_methods) if missing_methods.any?
71
+
72
+ super
73
+ end
74
+ end
75
+
76
+ include InitializePatch
77
+
78
+ def defmethod(method_name)
79
+ method_list = instance_variable_get(:@interface_methods)
80
+ method_list[method_name.to_sym] = { required_by: self }
81
+ end
82
+ end
83
+ end
84
+ end
@@ -39,7 +39,7 @@ module Flows
39
39
  # Disables contract check and transformation for current class and children.
40
40
  #
41
41
  # @param enable [Boolean] if true - contracts are disabled
42
- def skip_output_contract(enable = true)
42
+ def skip_output_contract(enable: true)
43
43
  @skip_output_contract_flag = enable
44
44
  end
45
45
  end
@@ -24,7 +24,7 @@ module Flows
24
24
  end
25
25
 
26
26
  def message
27
- shifted_error = @error.split("\n").map { |str| ' ' + str }.join("\n")
27
+ shifted_error = @error.split("\n").map { |str| " #{str}" }.join("\n")
28
28
 
29
29
  "Output contract for #{@klass} is violated.\n" \
30
30
  "Result:\n" \
@@ -5,8 +5,7 @@ module Flows
5
5
  class Flat < Tree
6
6
  # @api private
7
7
  class MethodReport
8
- attr_reader :root_node
9
- attr_reader :calculated_nodes
8
+ attr_reader :root_node, :calculated_nodes
10
9
 
11
10
  def initialize(root_node, *calculated_nodes)
12
11
  @root_node = root_node
@@ -106,7 +106,7 @@ module Flows
106
106
  children.map { |node| node.to_s(root_node) }
107
107
  .join("\n")
108
108
  .split("\n")
109
- .map { |str| '| ' + str }
109
+ .map { |str| "| #{str}" }
110
110
  end
111
111
  end
112
112
  end
@@ -5,8 +5,7 @@ module Flows
5
5
  class Tree < Report
6
6
  # @api private
7
7
  class Node
8
- attr_reader :subject
9
- attr_reader :executions
8
+ attr_reader :subject, :executions
10
9
 
11
10
  def initialize(subject:)
12
11
  @subject = subject
@@ -1,15 +1,15 @@
1
1
  module Flows
2
2
  class Railway
3
- # @api private
4
- Step = Struct.new(:name, :lambda, :next_step, keyword_init: true) do
5
- NODE_PREPROCESSOR = ->(input, _, _) { [[], input.unwrap] }
3
+ NODE_PREPROCESSOR = ->(input, _, _) { [[], input.unwrap] }
6
4
 
7
- NODE_POSTPROCESSOR = lambda do |output, context, meta|
8
- context[:last_step] = meta[:name]
5
+ NODE_POSTPROCESSOR = lambda do |output, context, meta|
6
+ context[:last_step] = meta[:name]
9
7
 
10
- output
11
- end
8
+ output
9
+ end
12
10
 
11
+ # @api private
12
+ Step = Struct.new(:name, :lambda, :next_step, keyword_init: true) do
13
13
  def to_node(method_source)
14
14
  Flows::Flow::Node.new(
15
15
  body: lambda || method_source.method(name),
@@ -141,6 +141,46 @@ module Flows
141
141
  # # steps implementations here
142
142
  # end
143
143
  #
144
+ # ## Simple injecting of nested pipelines
145
+ #
146
+ # If you provide some object which responds to `#call` instead of step name - this object will be used as a step body.
147
+ #
148
+ # class SubOperation < Flows::SharedContextPipeline
149
+ # step :hello
150
+ #
151
+ # def hello(**)
152
+ # ok(data: 'some data')
153
+ # end
154
+ # end
155
+ #
156
+ # class MainOperation < Flows::SharedContextPipeline
157
+ # step :init
158
+ # step SubOperation
159
+ #
160
+ # def init(**)
161
+ # ok(generated_by_init: true)
162
+ # end
163
+ # end
164
+ #
165
+ # MainOperation.call
166
+ # # => ok(generated_by_init: true, data: 'some data')
167
+ #
168
+ # You can use the same object multiple times in the same pipeline:
169
+ #
170
+ # step SubOperation
171
+ # step SubOperation
172
+ #
173
+ # If you need any input or output processing - refactor such step definition into normal step.
174
+ #
175
+ # This way has disadvantage: you cannot route to a such step because it has no explicit name.
176
+ # To handle this you can use alternative syntax:
177
+ #
178
+ # step :do_something, body: SubOperation
179
+ #
180
+ # Same features can be used with `mut_step`.
181
+ #
182
+ # This feature is primarily intended to simplify refactoring of big pipelines into smaller ones.
183
+ #
144
184
  # ## Wrappers
145
185
  #
146
186
  # Sometimes you have to execute some steps inside SQL-transaction or something like this.
@@ -224,6 +264,9 @@ module Flows
224
264
  #
225
265
  # You may want to have some logic to execute before all steps, or after all, or before each, or after each.
226
266
  # For example to inject generalized execution process logging.
267
+ #
268
+ # These callbacks are executed via `instance_exec` (in the context of instance).
269
+ #
227
270
  # To achieve this you can use callbacks:
228
271
  #
229
272
  # class MySCP < Flows::SharedContextPipeline
@@ -277,10 +320,10 @@ module Flows
277
320
  def call(**data) # rubocop:disable Metrics/MethodLength
278
321
  klass = self.class
279
322
  meta = {}
280
- context = { data: data, meta: meta, class: klass }
323
+ context = { data: data, meta: meta, class: klass, instance: self }
281
324
 
282
325
  klass.before_all_callbacks.each do |callback|
283
- callback.call(klass, data, meta)
326
+ instance_exec(klass, data, meta, &callback)
284
327
  end
285
328
 
286
329
  flow_result = @__flow.call(nil, context: context)
@@ -292,7 +335,7 @@ module Flows
292
335
  )
293
336
 
294
337
  klass.after_all_callbacks.reduce(final_result) do |result, callback|
295
- callback.call(klass, result, data, meta)
338
+ instance_exec(klass, result, data, meta, &callback)
296
339
  end
297
340
  end
298
341
  end
@@ -12,10 +12,7 @@ module Flows
12
12
 
13
13
  include SingletonVarsSetup
14
14
 
15
- attr_reader :before_all_callbacks
16
- attr_reader :after_all_callbacks
17
- attr_reader :before_each_callbacks
18
- attr_reader :after_each_callbacks
15
+ attr_reader :before_all_callbacks, :after_all_callbacks, :before_each_callbacks, :after_each_callbacks
19
16
 
20
17
  def before_all(&callback)
21
18
  before_all_callbacks << callback
@@ -16,15 +16,15 @@ module Flows
16
16
 
17
17
  attr_reader :tracks
18
18
 
19
- def step(name, router_def = DEFAULT_ROUTER_DEF, &lambda)
19
+ def step(name, router_def = DEFAULT_ROUTER_DEF, body: nil)
20
20
  tracks.add_step(
21
- Step.new(name: name, lambda: lambda, router_def: router_def)
21
+ Step.new(name: name, body: body, router_def: router_def)
22
22
  )
23
23
  end
24
24
 
25
- def mut_step(name, router_def = DEFAULT_ROUTER_DEF, &lambda)
25
+ def mut_step(name, router_def = DEFAULT_ROUTER_DEF, body: nil)
26
26
  tracks.add_step(
27
- MutationStep.new(name: name, lambda: lambda, router_def: router_def)
27
+ MutationStep.new(name: name, body: body, router_def: router_def)
28
28
  )
29
29
  end
30
30
 
@@ -8,7 +8,7 @@ module Flows
8
8
  class MutationStep < Step
9
9
  NODE_PREPROCESSOR = lambda do |_input, context, node_meta|
10
10
  context[:class].before_each_callbacks.each do |callback|
11
- callback.call(context[:class], node_meta[:name], context[:data], context[:meta])
11
+ context[:instance].instance_exec(context[:class], node_meta[:name], context[:data], context[:meta], &callback)
12
12
  end
13
13
 
14
14
  [[context[:data]], EMPTY_HASH]
@@ -20,7 +20,8 @@ module Flows
20
20
  else output ? EMPTY_OK : EMPTY_ERR
21
21
  end.tap do |result|
22
22
  context[:class].after_each_callbacks.each do |callback|
23
- callback.call(context[:class], node_meta[:name], result, context[:data], context[:meta])
23
+ context[:instance]
24
+ .instance_exec(context[:class], node_meta[:name], result, context[:data], context[:meta], &callback)
24
25
  end
25
26
  end
26
27
  end
@@ -3,12 +3,22 @@ module Flows
3
3
  EMPTY_ARRAY = [].freeze
4
4
 
5
5
  # @api private
6
- Step = Struct.new(:name, :lambda, :router_def, :next_step, keyword_init: true) do
6
+ Step = Struct.new(:name, :body, :router_def, :next_step, keyword_init: true) do
7
+ # :reek:ManualDispatch
8
+ def initialize(name:, body: nil, **rest)
9
+ if name.respond_to?(:call)
10
+ body = name
11
+ name = "#{body}+Step-Object-ID-#{object_id}"
12
+ end
13
+
14
+ super(name: name, body: body, **rest)
15
+ end
16
+
7
17
  def to_node(pipeline_class)
8
18
  klass = self.class
9
19
 
10
20
  Flows::Flow::Node.new(
11
- body: lambda || pipeline_class.method(name),
21
+ body: body || pipeline_class.method(name),
12
22
  router: router_def.to_router(next_step),
13
23
  meta: { name: name },
14
24
  preprocessor: klass::NODE_PREPROCESSOR,
@@ -21,7 +31,7 @@ module Flows
21
31
  :NODE_PREPROCESSOR,
22
32
  lambda do |_input, context, node_meta|
23
33
  context[:class].before_each_callbacks.each do |callback|
24
- callback.call(context[:class], node_meta[:name], context[:data], context[:meta])
34
+ context[:instance].instance_exec(context[:class], node_meta[:name], context[:data], context[:meta], &callback)
25
35
  end
26
36
 
27
37
  [EMPTY_ARRAY, context[:data]]
@@ -34,7 +44,8 @@ module Flows
34
44
  context[:data].merge!(result.instance_variable_get(:@data))
35
45
 
36
46
  context[:class].after_each_callbacks.each do |callback|
37
- callback.call(context[:class], node_meta[:name], result, context[:data], context[:meta])
47
+ context[:instance]
48
+ .instance_exec(context[:class], node_meta[:name], result, context[:data], context[:meta], &callback)
38
49
  end
39
50
 
40
51
  result
@@ -44,7 +44,7 @@ module Flows
44
44
  def make_track_entry_node
45
45
  MutationStep.new(
46
46
  name: @name,
47
- lambda: proc { true },
47
+ body: proc { true },
48
48
  router_def: TRACK_ENTRY_ROUTER_DEF,
49
49
  next_step: first_step_name
50
50
  ).to_node(nil)
@@ -2,8 +2,7 @@ module Flows
2
2
  class SharedContextPipeline
3
3
  # @api private
4
4
  class Wrap
5
- attr_reader :router_def
6
- attr_reader :tracks_definitions
5
+ attr_reader :router_def, :tracks_definitions
7
6
 
8
7
  # :reek:Attribute
9
8
  attr_accessor :next_step
@@ -24,14 +24,16 @@ module Flows
24
24
 
25
25
  # @api private
26
26
  module Migrator
27
- def self.call(from, to)
28
- parent_var_list = from.instance_variable_get(VAR_LIST_VAR_NAME)
29
- child_var_list = to.instance_variable_get(VAR_LIST_VAR_NAME) || []
27
+ # :reek:TooManyStatements is allowed here because it's impossible to split to smaller methods
28
+ def self.call(src_mod, dst_mod)
29
+ parent_var_list = src_mod.instance_variable_get(VAR_LIST_VAR_NAME)
30
+ child_var_list = dst_mod.instance_variable_get(VAR_LIST_VAR_NAME) || []
31
+ skip_list = parent_var_list & child_var_list
30
32
 
31
- to.instance_variable_set(VAR_LIST_VAR_NAME, child_var_list + parent_var_list)
33
+ dst_mod.instance_variable_set(VAR_LIST_VAR_NAME, (child_var_list + parent_var_list).uniq)
32
34
 
33
- parent_var_list.each do |name|
34
- to.instance_variable_set(name, from.instance_variable_get(name).dup)
35
+ (parent_var_list - skip_list).each do |name|
36
+ dst_mod.instance_variable_set(name, src_mod.instance_variable_get(name).dup)
35
37
  end
36
38
  end
37
39
  end
@@ -138,7 +138,8 @@ module Flows
138
138
  # * you can include `Mod` into `Mod2`, then include `Mod2` into `Mod3` -
139
139
  # desribed behavior works for include chain of any length.
140
140
  #
141
- # Moreover, this behaviour also works with `extend`, not only `include`.
141
+ # Each `include` generates a new prepend. Be careful about this when including
142
+ # generated module several times in the inheritance chain.
142
143
  #
143
144
  # @yield body for module which will be prepended
144
145
  # @return [Module] module to be included or extended into your module
@@ -160,7 +161,8 @@ module Flows
160
161
  mod.singleton_class.prepend(injector)
161
162
  end
162
163
 
163
- def make_injector_mod(module_to_prepend)
164
+ # :reek:TooManyStatements :reek:DuplicateMethodCall
165
+ def make_injector_mod(module_to_prepend) # rubocop:disable Metrics/MethodLength
164
166
  Module.new.tap do |injector|
165
167
  injector.define_method(:included) do |target_mod|
166
168
  if target_mod.class == Class
@@ -171,6 +173,16 @@ module Flows
171
173
 
172
174
  super(target_mod)
173
175
  end
176
+
177
+ injector.define_method(:extended) do |target_mod|
178
+ if target_mod.class == Class
179
+ target_mod.prepend(module_to_prepend)
180
+ else # Module
181
+ target_mod.singleton_class.prepend injector
182
+ end
183
+
184
+ super(target_mod)
185
+ end
174
186
  end
175
187
  end
176
188
  end
@@ -1,3 +1,3 @@
1
1
  module Flows
2
- VERSION = '0.5.1'.freeze
2
+ VERSION = '0.6.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flows
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Kolesnev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-06-29 00:00:00.000000000 Z
11
+ date: 2020-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -387,6 +387,7 @@ files:
387
387
  - ".reek.yml"
388
388
  - ".rspec"
389
389
  - ".rubocop.yml"
390
+ - ".rubocop_todo.yml"
390
391
  - ".ruby-version"
391
392
  - ".yardopts"
392
393
  - CHANGELOG.md
@@ -434,6 +435,7 @@ files:
434
435
  - bin/errors_cli/di_error_demo.rb
435
436
  - bin/errors_cli/flow_error_demo.rb
436
437
  - bin/errors_cli/flows_router_error_demo.rb
438
+ - bin/errors_cli/interface_error_demo.rb
437
439
  - bin/errors_cli/oc_error_demo.rb
438
440
  - bin/errors_cli/railway_error_demo.rb
439
441
  - bin/errors_cli/result_error_demo.rb
@@ -473,6 +475,7 @@ files:
473
475
  - lib/flows/plugin/dependency_injector/dependency_list.rb
474
476
  - lib/flows/plugin/dependency_injector/errors.rb
475
477
  - lib/flows/plugin/implicit_init.rb
478
+ - lib/flows/plugin/interface.rb
476
479
  - lib/flows/plugin/output_contract.rb
477
480
  - lib/flows/plugin/output_contract/dsl.rb
478
481
  - lib/flows/plugin/output_contract/errors.rb
@@ -529,7 +532,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
529
532
  requirements:
530
533
  - - ">="
531
534
  - !ruby/object:Gem::Version
532
- version: '0'
535
+ version: '2.5'
533
536
  required_rubygems_version: !ruby/object:Gem::Requirement
534
537
  requirements:
535
538
  - - ">="