flows 0.3.0 → 0.4.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.
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,21 @@
1
+ module Flows
2
+ class SharedContextPipeline
3
+ # @api private
4
+ class RouterDefinition
5
+ def initialize(routes = {})
6
+ @routes = routes
7
+ end
8
+
9
+ # :reek:ControlParameter is false positive here
10
+ def to_router(next_step)
11
+ final_routes = @routes.transform_values do |route|
12
+ next route unless route == :next
13
+
14
+ next_step || :end
15
+ end
16
+
17
+ ::Flows::Flow::Router::Custom.new(final_routes)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,46 @@
1
+ module Flows
2
+ class SharedContextPipeline
3
+ EMPTY_ARRAY = [].freeze
4
+
5
+ # @api private
6
+ Step = Struct.new(:name, :lambda, :router_def, :next_step, keyword_init: true) do
7
+ def to_node(pipeline_class)
8
+ klass = self.class
9
+
10
+ Flows::Flow::Node.new(
11
+ body: lambda || pipeline_class.method(name),
12
+ router: router_def.to_router(next_step),
13
+ meta: { name: name },
14
+ preprocessor: klass::NODE_PREPROCESSOR,
15
+ postprocessor: klass::NODE_POSTPROCESSOR
16
+ )
17
+ end
18
+ end
19
+
20
+ Step.const_set(
21
+ :NODE_PREPROCESSOR,
22
+ lambda do |_input, context, meta|
23
+ context[:last_step] = meta[:name]
24
+
25
+ context[:class].before_each_callbacks.each do |callback|
26
+ callback.call(context[:class], meta[:name], context[:data])
27
+ end
28
+
29
+ [EMPTY_ARRAY, context[:data]]
30
+ end
31
+ )
32
+
33
+ Step.const_set(
34
+ :NODE_POSTPROCESSOR,
35
+ lambda do |output, context, meta|
36
+ context[:data].merge!(output.instance_variable_get(:@data))
37
+
38
+ context[:class].after_each_callbacks.each do |callback|
39
+ callback.call(context[:class], meta[:name], context[:data], output)
40
+ end
41
+
42
+ output
43
+ end
44
+ )
45
+ end
46
+ end
@@ -0,0 +1,67 @@
1
+ module Flows
2
+ class SharedContextPipeline
3
+ # @api private
4
+ class Track
5
+ TRACK_ENTRY_ROUTER_DEF = RouterDefinition.new(
6
+ Flows::Result::Ok => :next,
7
+ Flows::Result::Err => :end
8
+ )
9
+
10
+ def initialize(name)
11
+ @name = name
12
+ @step_list = []
13
+ end
14
+
15
+ def initialize_dup(_other)
16
+ @step_list = @step_list.map(&:dup)
17
+ end
18
+
19
+ def add_step(name:, lambda: nil, router_def:)
20
+ step = Step.new(name: name, lambda: lambda, router_def: router_def)
21
+
22
+ last_step = @step_list.last
23
+ last_step.next_step = name if last_step
24
+
25
+ @step_list << step
26
+
27
+ self
28
+ end
29
+
30
+ def add_mutation_step(name:, lambda: nil, router_def:)
31
+ step = MutationStep.new(name: name, lambda: lambda, router_def: router_def)
32
+
33
+ last_step = @step_list.last
34
+ last_step.next_step = name if last_step
35
+
36
+ @step_list << step
37
+
38
+ self
39
+ end
40
+
41
+ def first_step_name
42
+ @step_list.first.name
43
+ end
44
+
45
+ def empty?
46
+ @step_list.empty?
47
+ end
48
+
49
+ def to_node_map(method_source)
50
+ @step_list.each_with_object(@name => make_track_entry_node) do |step, node_map|
51
+ node_map[step.name] = step.to_node(method_source)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def make_track_entry_node
58
+ MutationStep.new(
59
+ name: @name,
60
+ lambda: proc { true },
61
+ router_def: TRACK_ENTRY_ROUTER_DEF,
62
+ next_step: first_step_name
63
+ ).to_node(nil)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,46 @@
1
+ module Flows
2
+ class SharedContextPipeline
3
+ # @api private
4
+ class TrackList
5
+ attr_reader :current_track
6
+
7
+ def initialize
8
+ @tracks = { main: Track.new(:main) }
9
+ @current_track = :main
10
+ end
11
+
12
+ def initialize_dup(_other)
13
+ @tracks = @tracks.transform_values(&:dup)
14
+ end
15
+
16
+ def switch_track(track_name)
17
+ @tracks[track_name] ||= Track.new(track_name)
18
+ @current_track = track_name
19
+ end
20
+
21
+ def add_step(name:, lambda:, router_def:)
22
+ @tracks[@current_track].add_step(name: name, lambda: lambda, router_def: router_def)
23
+ end
24
+
25
+ def add_mutation_step(name:, lambda:, router_def:)
26
+ @tracks[@current_track].add_mutation_step(name: name, lambda: lambda, router_def: router_def)
27
+ end
28
+
29
+ def first_step_name
30
+ @tracks[:main].first_step_name
31
+ end
32
+
33
+ def main_track_empty?
34
+ @tracks[:main].empty?
35
+ end
36
+
37
+ def to_node_map(method_source)
38
+ @tracks.reduce({}) do |node_map, (_, track)|
39
+ node_map.merge!(
40
+ track.to_node_map(method_source)
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,17 @@
1
+ module Flows
2
+ # Namespace for low-level purely technical OOP helpers.
3
+ #
4
+ # Implementations here are relatively complex and require
5
+ # advanced understanding of Ruby's OOP and runtime.
6
+ #
7
+ # This module implements "hidden complexity" approach:
8
+ # hide most non-trivial parts of implementation inside
9
+ # small well-documented classes and modules.
10
+ #
11
+ # @since 0.4.0
12
+ module Util
13
+ end
14
+ end
15
+
16
+ require_relative 'util/prepend_to_class'
17
+ require_relative 'util/inheritable_singleton_vars'
@@ -0,0 +1,79 @@
1
+ module Flows
2
+ module Util
3
+ # Namespace for utility classes which allows you to define specific behaviour for
4
+ # [singleton variables](https://medium.com/@leo_hetsch/demystifying-singleton-classes-in-ruby-caf3fa4c9d91)
5
+ # in the context of inheritance.
6
+ #
7
+ # When you're writing some abstraction in Ruby one of the ways is to provide some base class and
8
+ # allow child classes to configure behaviour through class-level DSL. Something like that:
9
+ #
10
+ # class UserModel < BaseModel
11
+ # field :name
12
+ # field :username
13
+ # end
14
+ #
15
+ # The first problem here is where to store configuration values?
16
+ # In the most cases of such DSL it's singleton variables.
17
+ #
18
+ # But what will happen if we do something like this:
19
+ #
20
+ # class AdminModel < UserModel
21
+ # field :pgp_key
22
+ # end
23
+ #
24
+ # Which fields are defined for admin? `:name`, `:username` and `:pgp_key`?
25
+ # Or `:pgp_key` only? Both options are possible and can be implemented.
26
+ # But working with singleton variables is confusing and related code is confusing also.
27
+ # So, it's better to implement set of utility modules to define expected behaviour
28
+ # in a human-friendly format.
29
+ #
30
+ # The second problem is default values for singleton variables.
31
+ # In case of instance variables everything is simple:
32
+ # you have a constructor (`#initializer`) and it's the right place to set instance variables defaults.
33
+ # In case of singleton variables you can do it in `.extended` or `.included` callbacks.
34
+ # But this callback will not be executed on child classes. So, we have to add `.inherited` callback to the mix.
35
+ # Confusing? Yes. So, it's better to not think about it each time and
36
+ # use some helpers to explicitly define behaviour.
37
+ #
38
+ # Modules under this namespace provide helpers for defining defaults and inheritance strategy for your
39
+ # singleton variables.
40
+ #
41
+ # Each strategy here is using following way of injecting into yours abstract classes:
42
+ #
43
+ # class BaseSomething
44
+ # Flows::Util::InheritableSingletonVars::SomeStrategy.call(
45
+ # self,
46
+ # **rest_of_the_options_here
47
+ # )
48
+ # end
49
+ #
50
+ # In case of extensions and mixins:
51
+ #
52
+ # module MyExtension
53
+ # def self.extended(mod)
54
+ # Flows::Util::InheritableSingletonVars::SomeStrategy.call(
55
+ # mod,
56
+ # **rest_of_the_options_here
57
+ # )
58
+ # end
59
+ # end
60
+ #
61
+ # module MyMixin
62
+ # def self.included(mod)
63
+ # Flows::Util::InheritableSingletonVars::SomeStrategy.call(
64
+ # mod,
65
+ # **rest_of_the_options_here
66
+ # )
67
+ # end
68
+ # end
69
+ #
70
+ # Moreover, you can use multiple strategies in the same class.
71
+ #
72
+ # @since 0.4.0
73
+ module InheritableSingletonVars
74
+ end
75
+ end
76
+ end
77
+
78
+ require_relative './inheritable_singleton_vars/dup_strategy'
79
+ require_relative './inheritable_singleton_vars/isolation_strategy'
@@ -0,0 +1,109 @@
1
+ module Flows
2
+ module Util
3
+ module InheritableSingletonVars
4
+ # Strategy which uses `#dup` to copy variables to a child class.
5
+ #
6
+ # Can be applied several times to the same class.
7
+ #
8
+ # Can be applied in the middle of inheritance chain.
9
+ #
10
+ # When your value is a custom class you may need to adjust `#dup` behaviour.
11
+ # It can be done using `initialize_dup` method.
12
+ # Unfortunately it's not documented well in the standard library.
13
+ # So, [this will help you](https://blog.appsignal.com/2019/02/26/diving-into-dup-and-clone.html).
14
+ #
15
+ # @note If you change variables in a parent class after a child being defined
16
+ # it will have no effect on a child. Remember this when working in environments
17
+ # with tricky or experimental autoload mechanism.
18
+ #
19
+ # @see InheritableSingletonVars the parent module's documentation describes the problem this module solves.
20
+ #
21
+ # @since 0.4.0
22
+ module DupStrategy
23
+ VAR_LIST_VAR_NAME = :@inheritable_vars_with_dup
24
+
25
+ # @api private
26
+ module InheritanceCallback
27
+ def inherited(child_class)
28
+ DupStrategy.migrate(self, child_class)
29
+
30
+ super
31
+ end
32
+
33
+ def included(child_mod)
34
+ DupStrategy.migrate(self, child_mod)
35
+
36
+ child_mod.singleton_class.prepend(InheritanceCallback)
37
+
38
+ super
39
+ end
40
+
41
+ def extended(child_mod)
42
+ DupStrategy.migrate(self, child_mod)
43
+
44
+ child_mod.singleton_class.prepend(InheritanceCallback)
45
+
46
+ super
47
+ end
48
+ end
49
+
50
+ class << self
51
+ # Applies behaviour and defaults for singleton variables.
52
+ #
53
+ # @note Variable names should look like `:@var` or `'@var'`.
54
+ #
55
+ # @param klass [Class] target class.
56
+ # @param attrs_with_default [Hash<Symbol, String => Object>] keys are variable names,
57
+ # values are default values.
58
+ #
59
+ # @example
60
+ # class MyClass
61
+ # Flows::Util::InheritableSingletonVars::DupStrategy.call(
62
+ # self,
63
+ # :@my_list => []
64
+ # )
65
+ # end
66
+ def call(klass, attrs_with_default = {})
67
+ init_variables_with_default_values(klass, attrs_with_default)
68
+
69
+ var_names = attrs_with_default.keys.map(&:to_sym)
70
+ add_var_list(klass, var_names)
71
+
72
+ inject_inheritance_hook(klass)
73
+ end
74
+
75
+ # Moves variables between modules
76
+ #
77
+ # @api private
78
+ def migrate(from_mod, to_mod)
79
+ var_list = from_mod.instance_variable_get(VAR_LIST_VAR_NAME)
80
+ to_mod.instance_variable_set(VAR_LIST_VAR_NAME, var_list.dup)
81
+
82
+ var_list.each do |name|
83
+ to_mod.instance_variable_set(name, from_mod.instance_variable_get(name).dup)
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def init_variables_with_default_values(klass, attrs_with_default)
90
+ attrs_with_default.each do |name, default_value|
91
+ klass.instance_variable_set(name, default_value)
92
+ end
93
+ end
94
+
95
+ def add_var_list(klass, var_names)
96
+ watch_list = klass.instance_variable_get(VAR_LIST_VAR_NAME) || []
97
+ watch_list.concat(var_names)
98
+ klass.instance_variable_set(VAR_LIST_VAR_NAME, watch_list)
99
+ end
100
+
101
+ def inject_inheritance_hook(klass)
102
+ singleton = klass.singleton_class
103
+ singleton.prepend(InheritanceCallback) unless singleton.is_a?(InheritanceCallback)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,104 @@
1
+ module Flows
2
+ module Util
3
+ module InheritableSingletonVars
4
+ # Strategy which uses procs to generate initial values in target class and children.
5
+ #
6
+ # This strategy designed to make fully isolated singleton variables between classes.
7
+ #
8
+ # Can be applied several times to the same class.
9
+ #
10
+ # Can be applied in the middle of inheritance chain.
11
+ #
12
+ # @see InheritableSingletonVars the parent module's documentation describes the problem this module solves.
13
+ #
14
+ # @since 0.4.0
15
+ module IsolationStrategy
16
+ VAR_MAP_VAR_NAME = :@inheritable_vars_with_isolation
17
+
18
+ # @api private
19
+ module InheritanceCallback
20
+ def inherited(child_class)
21
+ IsolationStrategy.migrate(self, child_class)
22
+
23
+ super
24
+ end
25
+
26
+ def included(child_mod)
27
+ IsolationStrategy.migrate(self, child_mod)
28
+
29
+ child_mod.singleton_class.prepend InheritanceCallback
30
+
31
+ super
32
+ end
33
+
34
+ def extended(child_mod)
35
+ IsolationStrategy.migrate(self, child_mod)
36
+
37
+ child_mod.singleton_class.prepend InheritanceCallback
38
+
39
+ super
40
+ end
41
+ end
42
+
43
+ class << self
44
+ # Applies behaviour and defaults for singleton variables.
45
+ #
46
+ # @note Variable names should look like `:@var` or `'@var'`.
47
+ #
48
+ # @param klass [Class] target class.
49
+ # @param attrs_with_default [Hash<Symbol, String => Proc>] keys are variable names,
50
+ # values are procs or lambdas which return default values.
51
+ #
52
+ # @example
53
+ # class MyClass
54
+ # Flows::Util::InheritableSingletonVars::IsolationStrategy.call(
55
+ # self,
56
+ # :@my_list => -> { [] }
57
+ # )
58
+ # end
59
+ def call(klass, attrs_with_default = {})
60
+ init_variables_with_default_values(klass, attrs_with_default)
61
+
62
+ var_defaults = attrs_with_default
63
+ add_variables_to_store(klass, var_defaults)
64
+
65
+ inject_inheritance_hook(klass)
66
+ end
67
+
68
+ # Moves variables between modules
69
+ #
70
+ # @api private
71
+ def migrate(from_mod, to_mod)
72
+ new_var_map = from_mod.instance_variable_get(VAR_MAP_VAR_NAME).dup
73
+
74
+ to_mod.instance_variable_set(VAR_MAP_VAR_NAME, new_var_map)
75
+
76
+ new_var_map.each do |name, default_value_proc|
77
+ to_mod.instance_variable_set(name, default_value_proc.call)
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def init_variables_with_default_values(klass, attrs_with_default)
84
+ attrs_with_default.each do |name, default_value_proc|
85
+ klass.instance_variable_set(name, default_value_proc.call)
86
+ end
87
+ end
88
+
89
+ def add_variables_to_store(klass, var_defaults)
90
+ store = klass.instance_variable_get(VAR_MAP_VAR_NAME) || {}
91
+ next_store = store.merge(var_defaults)
92
+
93
+ klass.instance_variable_set(VAR_MAP_VAR_NAME, next_store)
94
+ end
95
+
96
+ def inject_inheritance_hook(klass)
97
+ singleton = klass.singleton_class
98
+ singleton.prepend(InheritanceCallback) unless singleton.is_a?(InheritanceCallback)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end