flows 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/{build.yml → test.yml} +5 -10
  3. data/.gitignore +1 -0
  4. data/.reek.yml +42 -0
  5. data/.rubocop.yml +20 -7
  6. data/.ruby-version +1 -1
  7. data/.yardopts +1 -0
  8. data/CHANGELOG.md +42 -0
  9. data/Gemfile +0 -6
  10. data/Gemfile.lock +139 -74
  11. data/README.md +158 -364
  12. data/Rakefile +35 -1
  13. data/bin/.rubocop.yml +5 -0
  14. data/bin/all_the_errors +47 -0
  15. data/bin/benchmark +73 -105
  16. data/bin/benchmark_cli/compare.rb +118 -0
  17. data/bin/benchmark_cli/compare/a_plus_b.rb +22 -0
  18. data/bin/benchmark_cli/compare/base.rb +45 -0
  19. data/bin/benchmark_cli/compare/command.rb +47 -0
  20. data/bin/benchmark_cli/compare/ten_steps.rb +22 -0
  21. data/bin/benchmark_cli/examples.rb +23 -0
  22. data/bin/benchmark_cli/examples/.rubocop.yml +19 -0
  23. data/bin/benchmark_cli/examples/a_plus_b/dry_do.rb +23 -0
  24. data/bin/benchmark_cli/examples/a_plus_b/dry_transaction.rb +17 -0
  25. data/bin/benchmark_cli/examples/a_plus_b/flows_do.rb +22 -0
  26. data/bin/benchmark_cli/examples/a_plus_b/flows_railway.rb +13 -0
  27. data/bin/benchmark_cli/examples/a_plus_b/flows_scp.rb +13 -0
  28. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_mut.rb +13 -0
  29. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_oc.rb +21 -0
  30. data/bin/benchmark_cli/examples/a_plus_b/trailblazer.rb +15 -0
  31. data/bin/benchmark_cli/examples/ten_steps/dry_do.rb +70 -0
  32. data/bin/benchmark_cli/examples/ten_steps/dry_transaction.rb +64 -0
  33. data/bin/benchmark_cli/examples/ten_steps/flows_do.rb +69 -0
  34. data/bin/benchmark_cli/examples/ten_steps/flows_railway.rb +58 -0
  35. data/bin/benchmark_cli/examples/ten_steps/flows_scp.rb +58 -0
  36. data/bin/benchmark_cli/examples/ten_steps/flows_scp_mut.rb +58 -0
  37. data/bin/benchmark_cli/examples/ten_steps/flows_scp_oc.rb +66 -0
  38. data/bin/benchmark_cli/examples/ten_steps/trailblazer.rb +60 -0
  39. data/bin/benchmark_cli/helpers.rb +12 -0
  40. data/bin/benchmark_cli/ruby.rb +15 -0
  41. data/bin/benchmark_cli/ruby/command.rb +38 -0
  42. data/bin/benchmark_cli/ruby/method_exec.rb +71 -0
  43. data/bin/benchmark_cli/ruby/self_class.rb +69 -0
  44. data/bin/benchmark_cli/ruby/structs.rb +90 -0
  45. data/bin/console +1 -0
  46. data/bin/docserver +7 -0
  47. data/bin/errors +118 -0
  48. data/bin/errors_cli/contract_error_demo.rb +49 -0
  49. data/bin/errors_cli/di_error_demo.rb +38 -0
  50. data/bin/errors_cli/flows_router_error_demo.rb +15 -0
  51. data/bin/errors_cli/oc_error_demo.rb +40 -0
  52. data/bin/errors_cli/railway_error_demo.rb +10 -0
  53. data/bin/errors_cli/result_error_demo.rb +13 -0
  54. data/bin/errors_cli/scp_error_demo.rb +17 -0
  55. data/docs/README.md +2 -186
  56. data/docs/_sidebar.md +0 -24
  57. data/docs/index.html +1 -1
  58. data/flows.gemspec +25 -2
  59. data/forspell.dict +9 -0
  60. data/lefthook.yml +9 -0
  61. data/lib/flows.rb +11 -5
  62. data/lib/flows/contract.rb +402 -0
  63. data/lib/flows/contract/array.rb +55 -0
  64. data/lib/flows/contract/case_eq.rb +41 -0
  65. data/lib/flows/contract/compose.rb +77 -0
  66. data/lib/flows/contract/either.rb +53 -0
  67. data/lib/flows/contract/error.rb +25 -0
  68. data/lib/flows/contract/hash.rb +75 -0
  69. data/lib/flows/contract/hash_of.rb +70 -0
  70. data/lib/flows/contract/helpers.rb +22 -0
  71. data/lib/flows/contract/predicate.rb +34 -0
  72. data/lib/flows/contract/transformer.rb +50 -0
  73. data/lib/flows/contract/tuple.rb +70 -0
  74. data/lib/flows/flow.rb +75 -7
  75. data/lib/flows/flow/node.rb +131 -0
  76. data/lib/flows/flow/router.rb +25 -0
  77. data/lib/flows/flow/router/custom.rb +54 -0
  78. data/lib/flows/flow/router/errors.rb +11 -0
  79. data/lib/flows/flow/router/simple.rb +20 -0
  80. data/lib/flows/plugin.rb +13 -0
  81. data/lib/flows/plugin/dependency_injector.rb +159 -0
  82. data/lib/flows/plugin/dependency_injector/dependency.rb +24 -0
  83. data/lib/flows/plugin/dependency_injector/dependency_definition.rb +16 -0
  84. data/lib/flows/plugin/dependency_injector/dependency_list.rb +57 -0
  85. data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
  86. data/lib/flows/plugin/implicit_init.rb +45 -0
  87. data/lib/flows/plugin/output_contract.rb +84 -0
  88. data/lib/flows/plugin/output_contract/dsl.rb +36 -0
  89. data/lib/flows/plugin/output_contract/errors.rb +74 -0
  90. data/lib/flows/plugin/output_contract/wrapper.rb +53 -0
  91. data/lib/flows/railway.rb +140 -37
  92. data/lib/flows/railway/dsl.rb +8 -19
  93. data/lib/flows/railway/errors.rb +8 -12
  94. data/lib/flows/railway/step.rb +24 -0
  95. data/lib/flows/railway/step_list.rb +38 -0
  96. data/lib/flows/result.rb +188 -2
  97. data/lib/flows/result/do.rb +160 -16
  98. data/lib/flows/result/err.rb +12 -6
  99. data/lib/flows/result/errors.rb +29 -17
  100. data/lib/flows/result/helpers.rb +25 -3
  101. data/lib/flows/result/ok.rb +12 -6
  102. data/lib/flows/shared_context_pipeline.rb +216 -0
  103. data/lib/flows/shared_context_pipeline/dsl.rb +63 -0
  104. data/lib/flows/shared_context_pipeline/errors.rb +17 -0
  105. data/lib/flows/shared_context_pipeline/mutation_step.rb +31 -0
  106. data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
  107. data/lib/flows/shared_context_pipeline/step.rb +46 -0
  108. data/lib/flows/shared_context_pipeline/track.rb +67 -0
  109. data/lib/flows/shared_context_pipeline/track_list.rb +46 -0
  110. data/lib/flows/util.rb +17 -0
  111. data/lib/flows/util/inheritable_singleton_vars.rb +79 -0
  112. data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +109 -0
  113. data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +104 -0
  114. data/lib/flows/util/prepend_to_class.rb +145 -0
  115. data/lib/flows/version.rb +1 -1
  116. metadata +233 -37
  117. data/bin/demo +0 -66
  118. data/bin/examples.rb +0 -195
  119. data/bin/profile_10steps +0 -106
  120. data/bin/ruby_benchmarks +0 -26
  121. data/docs/CNAME +0 -1
  122. data/docs/contributing/benchmarks_profiling.md +0 -3
  123. data/docs/contributing/local_development.md +0 -3
  124. data/docs/flow/direct_usage.md +0 -3
  125. data/docs/flow/general_idea.md +0 -3
  126. data/docs/operation/basic_usage.md +0 -1
  127. data/docs/operation/inject_steps.md +0 -3
  128. data/docs/operation/lambda_steps.md +0 -3
  129. data/docs/operation/result_shapes.md +0 -3
  130. data/docs/operation/routing_tracks.md +0 -3
  131. data/docs/operation/wrapping_steps.md +0 -3
  132. data/docs/overview/performance.md +0 -336
  133. data/docs/railway/basic_usage.md +0 -232
  134. data/docs/result_objects/basic_usage.md +0 -196
  135. data/docs/result_objects/do_notation.md +0 -139
  136. data/lib/flows/implicit_build.rb +0 -16
  137. data/lib/flows/node.rb +0 -27
  138. data/lib/flows/operation.rb +0 -55
  139. data/lib/flows/operation/builder.rb +0 -130
  140. data/lib/flows/operation/builder/build_router.rb +0 -37
  141. data/lib/flows/operation/dsl.rb +0 -93
  142. data/lib/flows/operation/errors.rb +0 -75
  143. data/lib/flows/operation/executor.rb +0 -78
  144. data/lib/flows/railway/builder.rb +0 -68
  145. data/lib/flows/railway/executor.rb +0 -23
  146. data/lib/flows/result_router.rb +0 -14
  147. data/lib/flows/router.rb +0 -22
@@ -0,0 +1,13 @@
1
+ module Flows
2
+ # Namespace for class behaviour extensions.
3
+ #
4
+ # Feel free to use it to empower your abstractions.
5
+ #
6
+ # @since 0.4.0
7
+ module Plugin
8
+ end
9
+ end
10
+
11
+ require_relative 'plugin/dependency_injector'
12
+ require_relative 'plugin/implicit_init'
13
+ require_relative 'plugin/output_contract'
@@ -0,0 +1,159 @@
1
+ require_relative 'dependency_injector/errors'
2
+ require_relative 'dependency_injector/dependency'
3
+ require_relative 'dependency_injector/dependency_definition'
4
+ require_relative 'dependency_injector/dependency_list'
5
+
6
+ module Flows
7
+ module Plugin
8
+ # Allows to inject dependencies on the initialization step.
9
+ #
10
+ # After including this module you inject dependencies by providing `:dependencies` key
11
+ # to your initializer:
12
+ #
13
+ # x = MyClass.new(dependencies: { my_dep: -> { 'Hi' } })
14
+ # x.my_dep
15
+ # # => 'Hi'
16
+ #
17
+ # Keys are dependency names. Dependency will be injected as
18
+ # a public method with dependency name. Values are dependencies itself.
19
+ #
20
+ # You can also require some dependencies to be present.
21
+ # If required dependency is missed - {MissingDependencyError} will be raised.
22
+ #
23
+ # If an optional dependency has no default - {MissingDependencyDefaultError} will be raised.
24
+ #
25
+ # For an optional dependency default value must be provided.
26
+ #
27
+ # You can provide a type for the dependency.
28
+ # Type check uses case equality (`===`).
29
+ # So, it works like Ruby's `case`.
30
+ # In case of type mismatch {UnexpectedDependencyTypeError} will be raised.
31
+ #
32
+ # dependency :name, type: String # name should be a string
33
+ #
34
+ # # by the way, you can use lambdas like in Ruby's `case`
35
+ # dependency :age, type: ->(x) { x.is_a?(Number) && x > 0 && x < 100 }
36
+ #
37
+ # If you're trying to inject undeclared dependency - {UnexpectedDependencyError} will be raised.
38
+ #
39
+ # Inheritance is supported and dependency definitions will be inherited into child classes.
40
+ #
41
+ # @example
42
+ #
43
+ # class MyClass
44
+ # include Flows::Plugin::DependencyInjector
45
+ #
46
+ # dependency :logger, required: true
47
+ # dependency :name, default: 'Boris', type: String # by default dependency is optional.
48
+ #
49
+ # attr_reader :data
50
+ #
51
+ # def initializer(data)
52
+ # @data = data
53
+ # end
54
+ #
55
+ # def log_the_name
56
+ # logger.call(name)
57
+ # end
58
+ # end
59
+ #
60
+ # class Logger
61
+ # def self.call(msg)
62
+ # puts msg
63
+ # end
64
+ # end
65
+ #
66
+ # x = MyClass.new('DATA', dependencies: {
67
+ # logger: Logger
68
+ # })
69
+ #
70
+ # x.data
71
+ # # => 'DATA'
72
+ #
73
+ # x.name
74
+ # # => 'Boris'
75
+ #
76
+ # x.logger.call('Hello')
77
+ # # prints 'Hello'
78
+ #
79
+ # x.log_the_name
80
+ # # prints 'Boris'
81
+ module DependencyInjector
82
+ # Placeholder for empty type. We cannot use `nil` because value can be `nil`.
83
+ NO_TYPE = :__no_type__
84
+
85
+ # Placeholder for empty default. We cannot use `nil` because value can be `nil`.
86
+ NO_DEFAULT = :__no_default__
87
+
88
+ # Placeholder for empty value. We cannot use `nil` because value can be `nil`.
89
+ NO_VALUE = :__no_value__
90
+
91
+ Flows::Util::InheritableSingletonVars::DupStrategy.call(
92
+ self,
93
+ '@dependencies' => {}
94
+ )
95
+
96
+ # @api private
97
+ module DSL
98
+ attr_reader :dependencies
99
+
100
+ # `:reek:BooleanParameter` disabled here because it's not applicable for DSLs
101
+ def dependency(name, required: false, default: NO_DEFAULT, type: NO_TYPE)
102
+ dependencies[name] = DependencyDefinition.new(
103
+ name: name,
104
+ required: required,
105
+ default: default,
106
+ type: type,
107
+ klass: self
108
+ )
109
+ end
110
+ end
111
+
112
+ # @api private
113
+ #
114
+ # `:reek:UtilityFunction` and `:reek:FeatureEnvy` are disabled here because Reek does not
115
+ # know about inheritance callback stuff.
116
+ module InheritanceCallback
117
+ def included(mod)
118
+ mod.extend(DSL)
119
+
120
+ mod.singleton_class.prepend(InheritanceCallback) if mod.class == Module
121
+
122
+ super
123
+ end
124
+
125
+ def extended(mod)
126
+ mod.extend(DSL)
127
+
128
+ mod.singleton_class.prepend(InheritanceCallback) if mod.class == Module
129
+
130
+ super
131
+ end
132
+ end
133
+
134
+ singleton_class.prepend InheritanceCallback
135
+
136
+ # @api private
137
+ module InitializePatch
138
+ def initialize(*args, **kwargs, &block) # rubocop:disable Metrics/MethodLength
139
+ klass = self.class
140
+ DependencyList.new(
141
+ klass: klass,
142
+ definitions: klass.dependencies,
143
+ provided_values: kwargs[:dependencies].dup || {}
144
+ ).inject_to(self)
145
+
146
+ filtered_kwargs = kwargs.reject { |key, _| key == :dependencies }
147
+
148
+ if filtered_kwargs.empty? # https://bugs.ruby-lang.org/issues/14415
149
+ super(*args, &block)
150
+ else
151
+ super(*args, **filtered_kwargs, &block)
152
+ end
153
+ end
154
+ end
155
+
156
+ Flows::Util::PrependToClass.call(self, InitializePatch)
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,24 @@
1
+ module Flows
2
+ module Plugin
3
+ module DependencyInjector
4
+ # Resolves dependency on initialization and can inject it into class instance.
5
+ #
6
+ # @api private
7
+ Dependency = Struct.new(:name, :definition, :provided_value, :value, :klass, keyword_init: true) do
8
+ def initialize(*)
9
+ super
10
+
11
+ self.value = provided_value == NO_VALUE ? definition.default : provided_value
12
+ type = definition.type
13
+
14
+ raise UnexpectedDependencyTypeError.new(klass, name, value, type) if type != NO_TYPE && !(type === value) # rubocop:disable Style/CaseEquality
15
+ end
16
+
17
+ def inject_to(instance)
18
+ value = self.value
19
+ instance.define_singleton_method(name) { value }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,16 @@
1
+ module Flows
2
+ module Plugin
3
+ module DependencyInjector
4
+ # Struct for storing dependency definitions.
5
+ #
6
+ # @api private
7
+ DependencyDefinition = Struct.new(:name, :required, :default, :type, :klass, keyword_init: true) do
8
+ def initialize(*)
9
+ super
10
+
11
+ raise MissingDependencyDefaultError.new(klass, name) if !required && (default == NO_DEFAULT)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,57 @@
1
+ module Flows
2
+ module Plugin
3
+ module DependencyInjector
4
+ # Resolves dependencies on initialization and can inject it into class instance.
5
+ #
6
+ # @api private
7
+ class DependencyList
8
+ attr_reader :definitions
9
+ attr_reader :provided_values
10
+ attr_reader :dependencies
11
+
12
+ def initialize(klass:, definitions:, provided_values:)
13
+ @klass = klass
14
+ @definitions = definitions
15
+ @provided_values = provided_values.dup.tap { |pv| pv.default = NO_VALUE }
16
+
17
+ check_missing_dependencies
18
+ check_unexpected_dependencies
19
+ resolve_dependencies
20
+ end
21
+
22
+ def inject_to(instance)
23
+ dependencies.each { |dep| dep.inject_to(instance) }
24
+ end
25
+
26
+ private
27
+
28
+ def required_dependencies
29
+ definitions.select { |_, definition| definition.required }.keys
30
+ end
31
+
32
+ def check_missing_dependencies
33
+ missing = required_dependencies - provided_values.keys
34
+
35
+ raise MissingDependencyError.new(@klass, missing) if missing.any?
36
+ end
37
+
38
+ def check_unexpected_dependencies
39
+ unexpected = provided_values.keys - definitions.keys
40
+
41
+ raise UnexpectedDependencyError.new(@klass, unexpected) if unexpected.any?
42
+ end
43
+
44
+ def resolve_dependencies
45
+ @dependencies = definitions.map do |name, definition|
46
+ Dependency.new(
47
+ klass: @klass,
48
+ name: name,
49
+ definition: definition,
50
+ provided_value: provided_values[name]
51
+ )
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,58 @@
1
+ module Flows
2
+ module Plugin
3
+ module DependencyInjector
4
+ # Base error class for dependency injection errors.
5
+ class Error < ::Flows::Error; end
6
+
7
+ # Raised when you're missed some dependency.
8
+ class MissingDependencyError < Error
9
+ def initialize(klass, names)
10
+ @klass = klass
11
+ @names = names
12
+ end
13
+
14
+ def message
15
+ "Missing dependency(ies) for #{@klass}: #{@names.map(&:to_s).join(', ')}"
16
+ end
17
+ end
18
+
19
+ # Raised when you're providing undeclared dependency.
20
+ class UnexpectedDependencyError < Error
21
+ def initialize(klass, names)
22
+ @klass = klass
23
+ @names = names
24
+ end
25
+
26
+ def message
27
+ "Unexpected dependency(ies) for #{@klass}: #{@names.map(&:to_s).join(', ')}"
28
+ end
29
+ end
30
+
31
+ # Raised when dependency has unexpected type.
32
+ class UnexpectedDependencyTypeError < Error
33
+ def initialize(klass, name, value, type)
34
+ @klass = klass
35
+ @_name = name
36
+ @value = value
37
+ @_type = type
38
+ end
39
+
40
+ def message
41
+ "#{@_name} dependency for #{@klass} has wrong type, must conform `#{@_type.inspect}`: `#{@value.inspect}`"
42
+ end
43
+ end
44
+
45
+ # Raised when an optional dependency has no default value.
46
+ class MissingDependencyDefaultError < Error
47
+ def initialize(klass, name)
48
+ @klass = klass
49
+ @_name = name
50
+ end
51
+
52
+ def message
53
+ "Optional dependency #{@_name} for #{@klass} has no default value"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,45 @@
1
+ module Flows
2
+ module Plugin
3
+ # Class extension with method `MyClass.call` which works like `MyClass.new.call`.
4
+ #
5
+ # @note This module must be injected into target class using `extend`, not `include`.
6
+ #
7
+ # @note Class inheritance is supported: each child class will inherit behaviour, but not data.
8
+ #
9
+ # @example Extending a class
10
+ # class SomeClass
11
+ # extend Flows::Plugin::ImplicitInit
12
+ #
13
+ # def initialize(param: 'default')
14
+ # @param = param
15
+ # end
16
+ #
17
+ # def call
18
+ # @param
19
+ # end
20
+ # end
21
+ #
22
+ # SomeClass.call
23
+ # # => 'default'
24
+ #
25
+ # SomeClass.default_instance.call
26
+ # # => 'default'
27
+ # @since 0.4.0
28
+ module ImplicitInit
29
+ # Contains memoized instance of a host class or `nil`.
30
+ attr_reader :default_instance
31
+
32
+ # Creates an instance of a host class by calling `new` without arguments and
33
+ # calls `#call` method on the instance with provided parameters and block.
34
+ #
35
+ # After first invocation the instance will be memoized in {.default_instance}.
36
+ #
37
+ # Child classes have separate default instances.
38
+ def call(*args, **kwargs, &block)
39
+ @default_instance ||= new
40
+
41
+ default_instance.call(*args, **kwargs, &block)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,84 @@
1
+ require_relative 'output_contract/errors'
2
+ require_relative 'output_contract/dsl'
3
+ require_relative 'output_contract/wrapper'
4
+
5
+ module Flows
6
+ module Plugin
7
+ # Allows to make a contract check and transformation for `#call` method execution in any class.
8
+ #
9
+ # Plugin applies a wrapper to a `#call` instance method.
10
+ # This wrapper will do the following:
11
+ #
12
+ # * check that {Result} instance is returned by `#call`
13
+ # * check that returned {Result#status} is expected
14
+ # * check that returned result data conforms {Contract} assigned
15
+ # to a particular result type and status
16
+ # * applies contract transform to the returned data
17
+ # * returns {Result} with the same status and type,
18
+ # wraps transformed data inside.
19
+ #
20
+ # Plugin provides DSL to express expected result statuses and assigned contracts.
21
+ # Contracts definition reuses {Contract.make} to execute block and get a contract.
22
+ #
23
+ # * `success_with(status, &block)` - defines contract for a successful result with status `status`.
24
+ # * `failure_with(status, &block)` - defines contract for a failure result with status `status`.
25
+ #
26
+ # @example with one possible output contract
27
+ # class DoJob
28
+ # include Flows::Result::Helpers
29
+ # include Flows::Plugin::OutputContract
30
+ #
31
+ # success_with :ok do
32
+ # Integer
33
+ # end
34
+ #
35
+ # def call(a, b)
36
+ # ok_data(a + b)
37
+ # end
38
+ # end
39
+ #
40
+ # DoJob.new.call(1, 2).unwrap
41
+ # # => 3
42
+ #
43
+ # DoJob.new.call('a', 'b')
44
+ # # Flows::Contract::Error exception raised
45
+ #
46
+ # @example with multiple contracts
47
+ # class DoJob
48
+ # include Flows::Result::Helpers
49
+ # include Flows::Plugin::OutputContract
50
+ #
51
+ # success_with :int_sum do
52
+ # Integer
53
+ # end
54
+ #
55
+ # success_with :float_sum do
56
+ # Float
57
+ # end
58
+ #
59
+ # failure_with :err do
60
+ # hash_of(
61
+ # key: Symbol,
62
+ # msg: String
63
+ # )
64
+ # end
65
+ #
66
+ # def call(a, b)
67
+ # if a.is_a?(Float) || b.is_a?(Float)
68
+ # ok_data(a + b, status: :float_sum)
69
+ # elsif a.is_a?(Integer) && b.is_a?(Integer)
70
+ # ok_data(a + b, status: :int_sum)
71
+ # else
72
+ # err(key: :unexpected_type, msg: "Unexpected argument types")
73
+ # end
74
+ # end
75
+ # end
76
+ module OutputContract
77
+ # @api private
78
+ def self.included(mod)
79
+ mod.extend(DSL)
80
+ mod.prepend(Wrapper)
81
+ end
82
+ end
83
+ end
84
+ end