flows 0.2.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/{build.yml → test.yml} +5 -10
  3. data/.gitignore +9 -1
  4. data/.mdlrc +1 -1
  5. data/.reek.yml +54 -0
  6. data/.rubocop.yml +26 -7
  7. data/.rubocop_todo.yml +27 -0
  8. data/.ruby-version +1 -1
  9. data/.yardopts +1 -0
  10. data/CHANGELOG.md +81 -0
  11. data/Gemfile +0 -6
  12. data/README.md +167 -363
  13. data/Rakefile +35 -1
  14. data/bin/.rubocop.yml +5 -0
  15. data/bin/all_the_errors +55 -0
  16. data/bin/benchmark +73 -105
  17. data/bin/benchmark_cli/compare.rb +118 -0
  18. data/bin/benchmark_cli/compare/a_plus_b.rb +22 -0
  19. data/bin/benchmark_cli/compare/base.rb +45 -0
  20. data/bin/benchmark_cli/compare/command.rb +47 -0
  21. data/bin/benchmark_cli/compare/ten_steps.rb +22 -0
  22. data/bin/benchmark_cli/examples.rb +23 -0
  23. data/bin/benchmark_cli/examples/.rubocop.yml +22 -0
  24. data/bin/benchmark_cli/examples/a_plus_b/dry_do.rb +23 -0
  25. data/bin/benchmark_cli/examples/a_plus_b/dry_transaction.rb +17 -0
  26. data/bin/benchmark_cli/examples/a_plus_b/flows_do.rb +22 -0
  27. data/bin/benchmark_cli/examples/a_plus_b/flows_railway.rb +13 -0
  28. data/bin/benchmark_cli/examples/a_plus_b/flows_scp.rb +13 -0
  29. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_mut.rb +13 -0
  30. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_oc.rb +21 -0
  31. data/bin/benchmark_cli/examples/a_plus_b/trailblazer.rb +15 -0
  32. data/bin/benchmark_cli/examples/ten_steps/dry_do.rb +70 -0
  33. data/bin/benchmark_cli/examples/ten_steps/dry_transaction.rb +64 -0
  34. data/bin/benchmark_cli/examples/ten_steps/flows_do.rb +69 -0
  35. data/bin/benchmark_cli/examples/ten_steps/flows_railway.rb +58 -0
  36. data/bin/benchmark_cli/examples/ten_steps/flows_scp.rb +58 -0
  37. data/bin/benchmark_cli/examples/ten_steps/flows_scp_mut.rb +58 -0
  38. data/bin/benchmark_cli/examples/ten_steps/flows_scp_oc.rb +66 -0
  39. data/bin/benchmark_cli/examples/ten_steps/trailblazer.rb +60 -0
  40. data/bin/benchmark_cli/helpers.rb +12 -0
  41. data/bin/benchmark_cli/ruby.rb +15 -0
  42. data/bin/benchmark_cli/ruby/command.rb +38 -0
  43. data/bin/benchmark_cli/ruby/method_exec.rb +71 -0
  44. data/bin/benchmark_cli/ruby/self_class.rb +69 -0
  45. data/bin/benchmark_cli/ruby/structs.rb +90 -0
  46. data/bin/console +1 -0
  47. data/bin/docserver +7 -0
  48. data/bin/errors +138 -0
  49. data/bin/errors_cli/contract_error_demo.rb +49 -0
  50. data/bin/errors_cli/di_error_demo.rb +38 -0
  51. data/bin/errors_cli/flow_error_demo.rb +22 -0
  52. data/bin/errors_cli/flows_router_error_demo.rb +15 -0
  53. data/bin/errors_cli/interface_error_demo.rb +17 -0
  54. data/bin/errors_cli/oc_error_demo.rb +40 -0
  55. data/bin/errors_cli/railway_error_demo.rb +10 -0
  56. data/bin/errors_cli/result_error_demo.rb +13 -0
  57. data/bin/errors_cli/scp_error_demo.rb +17 -0
  58. data/docs/README.md +3 -187
  59. data/docs/_sidebar.md +0 -24
  60. data/docs/index.html +1 -1
  61. data/flows.gemspec +27 -2
  62. data/forspell.dict +9 -0
  63. data/lefthook.yml +9 -0
  64. data/lib/flows.rb +11 -5
  65. data/lib/flows/contract.rb +402 -0
  66. data/lib/flows/contract/array.rb +55 -0
  67. data/lib/flows/contract/case_eq.rb +43 -0
  68. data/lib/flows/contract/compose.rb +77 -0
  69. data/lib/flows/contract/either.rb +53 -0
  70. data/lib/flows/contract/error.rb +24 -0
  71. data/lib/flows/contract/hash.rb +75 -0
  72. data/lib/flows/contract/hash_of.rb +70 -0
  73. data/lib/flows/contract/helpers.rb +22 -0
  74. data/lib/flows/contract/predicate.rb +34 -0
  75. data/lib/flows/contract/transformer.rb +50 -0
  76. data/lib/flows/contract/tuple.rb +70 -0
  77. data/lib/flows/flow.rb +96 -7
  78. data/lib/flows/flow/errors.rb +29 -0
  79. data/lib/flows/flow/node.rb +132 -0
  80. data/lib/flows/flow/router.rb +29 -0
  81. data/lib/flows/flow/router/custom.rb +59 -0
  82. data/lib/flows/flow/router/errors.rb +11 -0
  83. data/lib/flows/flow/router/simple.rb +25 -0
  84. data/lib/flows/plugin.rb +15 -0
  85. data/lib/flows/plugin/dependency_injector.rb +170 -0
  86. data/lib/flows/plugin/dependency_injector/dependency.rb +24 -0
  87. data/lib/flows/plugin/dependency_injector/dependency_definition.rb +16 -0
  88. data/lib/flows/plugin/dependency_injector/dependency_list.rb +55 -0
  89. data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
  90. data/lib/flows/plugin/implicit_init.rb +45 -0
  91. data/lib/flows/plugin/interface.rb +84 -0
  92. data/lib/flows/plugin/output_contract.rb +85 -0
  93. data/lib/flows/plugin/output_contract/dsl.rb +48 -0
  94. data/lib/flows/plugin/output_contract/errors.rb +74 -0
  95. data/lib/flows/plugin/output_contract/wrapper.rb +55 -0
  96. data/lib/flows/plugin/profiler.rb +114 -0
  97. data/lib/flows/plugin/profiler/injector.rb +35 -0
  98. data/lib/flows/plugin/profiler/report.rb +48 -0
  99. data/lib/flows/plugin/profiler/report/events.rb +43 -0
  100. data/lib/flows/plugin/profiler/report/flat.rb +41 -0
  101. data/lib/flows/plugin/profiler/report/flat/method_report.rb +80 -0
  102. data/lib/flows/plugin/profiler/report/raw.rb +15 -0
  103. data/lib/flows/plugin/profiler/report/tree.rb +98 -0
  104. data/lib/flows/plugin/profiler/report/tree/calculated_node.rb +116 -0
  105. data/lib/flows/plugin/profiler/report/tree/node.rb +34 -0
  106. data/lib/flows/plugin/profiler/wrapper.rb +53 -0
  107. data/lib/flows/railway.rb +140 -34
  108. data/lib/flows/railway/dsl.rb +8 -18
  109. data/lib/flows/railway/errors.rb +8 -12
  110. data/lib/flows/railway/step.rb +24 -0
  111. data/lib/flows/railway/step_list.rb +38 -0
  112. data/lib/flows/result.rb +188 -2
  113. data/lib/flows/result/do.rb +158 -16
  114. data/lib/flows/result/err.rb +12 -6
  115. data/lib/flows/result/errors.rb +29 -17
  116. data/lib/flows/result/helpers.rb +25 -3
  117. data/lib/flows/result/ok.rb +12 -6
  118. data/lib/flows/shared_context_pipeline.rb +342 -0
  119. data/lib/flows/shared_context_pipeline/dsl.rb +12 -0
  120. data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +35 -0
  121. data/lib/flows/shared_context_pipeline/dsl/tracks.rb +52 -0
  122. data/lib/flows/shared_context_pipeline/errors.rb +17 -0
  123. data/lib/flows/shared_context_pipeline/mutation_step.rb +30 -0
  124. data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
  125. data/lib/flows/shared_context_pipeline/step.rb +55 -0
  126. data/lib/flows/shared_context_pipeline/track.rb +54 -0
  127. data/lib/flows/shared_context_pipeline/track_list.rb +51 -0
  128. data/lib/flows/shared_context_pipeline/wrap.rb +73 -0
  129. data/lib/flows/util.rb +17 -0
  130. data/lib/flows/util/inheritable_singleton_vars.rb +86 -0
  131. data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +100 -0
  132. data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +91 -0
  133. data/lib/flows/util/prepend_to_class.rb +191 -0
  134. data/lib/flows/version.rb +1 -1
  135. metadata +253 -38
  136. data/Gemfile.lock +0 -174
  137. data/bin/demo +0 -66
  138. data/bin/examples.rb +0 -195
  139. data/bin/profile_10steps +0 -106
  140. data/bin/ruby_benchmarks +0 -26
  141. data/docs/CNAME +0 -1
  142. data/docs/contributing/benchmarks_profiling.md +0 -3
  143. data/docs/contributing/local_development.md +0 -3
  144. data/docs/flow/direct_usage.md +0 -3
  145. data/docs/flow/general_idea.md +0 -3
  146. data/docs/operation/basic_usage.md +0 -1
  147. data/docs/operation/inject_steps.md +0 -3
  148. data/docs/operation/lambda_steps.md +0 -3
  149. data/docs/operation/result_shapes.md +0 -3
  150. data/docs/operation/routing_tracks.md +0 -3
  151. data/docs/operation/wrapping_steps.md +0 -3
  152. data/docs/overview/performance.md +0 -336
  153. data/docs/railway/basic_usage.md +0 -232
  154. data/docs/result_objects/basic_usage.md +0 -196
  155. data/docs/result_objects/do_notation.md +0 -139
  156. data/lib/flows/node.rb +0 -27
  157. data/lib/flows/operation.rb +0 -52
  158. data/lib/flows/operation/builder.rb +0 -130
  159. data/lib/flows/operation/builder/build_router.rb +0 -37
  160. data/lib/flows/operation/dsl.rb +0 -93
  161. data/lib/flows/operation/errors.rb +0 -75
  162. data/lib/flows/operation/executor.rb +0 -78
  163. data/lib/flows/railway/builder.rb +0 -68
  164. data/lib/flows/railway/executor.rb +0 -23
  165. data/lib/flows/result_router.rb +0 -14
  166. data/lib/flows/router.rb +0 -22
@@ -0,0 +1,12 @@
1
+ require_relative 'dsl/tracks'
2
+ require_relative 'dsl/callbacks'
3
+
4
+ module Flows
5
+ class SharedContextPipeline
6
+ # @api private
7
+ module DSL
8
+ include Tracks
9
+ include Callbacks
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ module Flows
2
+ class SharedContextPipeline
3
+ module DSL
4
+ # @api private
5
+ module Callbacks
6
+ SingletonVarsSetup = Flows::Util::InheritableSingletonVars::DupStrategy.make_module(
7
+ '@before_all_callbacks' => [],
8
+ '@after_all_callbacks' => [],
9
+ '@before_each_callbacks' => [],
10
+ '@after_each_callbacks' => []
11
+ )
12
+
13
+ include SingletonVarsSetup
14
+
15
+ attr_reader :before_all_callbacks, :after_all_callbacks, :before_each_callbacks, :after_each_callbacks
16
+
17
+ def before_all(&callback)
18
+ before_all_callbacks << callback
19
+ end
20
+
21
+ def after_all(&callback)
22
+ after_all_callbacks << callback
23
+ end
24
+
25
+ def before_each(&callback)
26
+ before_each_callbacks << callback
27
+ end
28
+
29
+ def after_each(&callback)
30
+ after_each_callbacks << callback
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,52 @@
1
+ module Flows
2
+ class SharedContextPipeline
3
+ module DSL
4
+ # @api private
5
+ module Tracks
6
+ DEFAULT_ROUTER_DEF = RouterDefinition.new(
7
+ Flows::Result::Ok => :next,
8
+ Flows::Result::Err => :end
9
+ )
10
+
11
+ SingletonVarsSetup = Flows::Util::InheritableSingletonVars::DupStrategy.make_module(
12
+ '@tracks' => TrackList.new
13
+ )
14
+
15
+ include SingletonVarsSetup
16
+
17
+ attr_reader :tracks
18
+
19
+ def step(name, router_def = DEFAULT_ROUTER_DEF, body: nil)
20
+ tracks.add_step(
21
+ Step.new(name: name, body: body, router_def: router_def)
22
+ )
23
+ end
24
+
25
+ def mut_step(name, router_def = DEFAULT_ROUTER_DEF, body: nil)
26
+ tracks.add_step(
27
+ MutationStep.new(name: name, body: body, router_def: router_def)
28
+ )
29
+ end
30
+
31
+ def wrap(name, router_def = DEFAULT_ROUTER_DEF, &tracks_definitions)
32
+ tracks.add_step(
33
+ Wrap.new(method_name: name, router_def: router_def, &tracks_definitions)
34
+ )
35
+ end
36
+
37
+ def track(name, &block)
38
+ track_before = tracks.current_track
39
+
40
+ tracks.switch_track(name)
41
+ instance_exec(&block)
42
+ tracks.switch_track(track_before)
43
+ end
44
+
45
+ # :reek:UtilityFunction is allowed here
46
+ def routes(routes_def)
47
+ RouterDefinition.new(routes_def)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,17 @@
1
+ module Flows
2
+ class SharedContextPipeline
3
+ # Base error class for {SharedContextPipeline} errors.
4
+ class Error < StandardError; end
5
+
6
+ # Raised when initializing {SharedContextPipeline} with no steps.
7
+ class NoStepsError < Error
8
+ def initialize(klass)
9
+ @klass = klass
10
+ end
11
+
12
+ def message
13
+ "No steps defined for main track in #{@klass}"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ module Flows
2
+ class SharedContextPipeline
3
+ EMPTY_HASH = {}.freeze
4
+ EMPTY_OK = Flows::Result::Ok.new({}.freeze).freeze
5
+ EMPTY_ERR = Flows::Result::Err.new({}.freeze).freeze
6
+
7
+ # @api private
8
+ class MutationStep < Step
9
+ NODE_PREPROCESSOR = lambda do |_input, context, node_meta|
10
+ context[:class].before_each_callbacks.each do |callback|
11
+ context[:instance].instance_exec(context[:class], node_meta[:name], context[:data], context[:meta], &callback)
12
+ end
13
+
14
+ [[context[:data]], EMPTY_HASH]
15
+ end
16
+
17
+ NODE_POSTPROCESSOR = lambda do |output, context, node_meta|
18
+ case output
19
+ when Flows::Result then output
20
+ else output ? EMPTY_OK : EMPTY_ERR
21
+ end.tap do |result|
22
+ context[:class].after_each_callbacks.each do |callback|
23
+ context[:instance]
24
+ .instance_exec(context[:class], node_meta[:name], result, context[:data], context[:meta], &callback)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -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,55 @@
1
+ module Flows
2
+ class SharedContextPipeline
3
+ EMPTY_ARRAY = [].freeze
4
+
5
+ # @api private
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
+
17
+ def to_node(pipeline_class)
18
+ klass = self.class
19
+
20
+ Flows::Flow::Node.new(
21
+ body: body || pipeline_class.method(name),
22
+ router: router_def.to_router(next_step),
23
+ meta: { name: name },
24
+ preprocessor: klass::NODE_PREPROCESSOR,
25
+ postprocessor: klass::NODE_POSTPROCESSOR
26
+ )
27
+ end
28
+ end
29
+
30
+ Step.const_set(
31
+ :NODE_PREPROCESSOR,
32
+ lambda do |_input, context, node_meta|
33
+ context[:class].before_each_callbacks.each do |callback|
34
+ context[:instance].instance_exec(context[:class], node_meta[:name], context[:data], context[:meta], &callback)
35
+ end
36
+
37
+ [EMPTY_ARRAY, context[:data]]
38
+ end
39
+ )
40
+
41
+ Step.const_set(
42
+ :NODE_POSTPROCESSOR,
43
+ lambda do |result, context, node_meta|
44
+ context[:data].merge!(result.instance_variable_get(:@data))
45
+
46
+ context[:class].after_each_callbacks.each do |callback|
47
+ context[:instance]
48
+ .instance_exec(context[:class], node_meta[:name], result, context[:data], context[:meta], &callback)
49
+ end
50
+
51
+ result
52
+ end
53
+ )
54
+ end
55
+ end
@@ -0,0 +1,54 @@
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(step)
20
+ last_step = @step_list.last
21
+ last_step.next_step = step.name if last_step
22
+
23
+ @step_list << step
24
+
25
+ self
26
+ end
27
+
28
+ def first_step_name
29
+ @step_list.first.name
30
+ end
31
+
32
+ def empty?
33
+ @step_list.empty?
34
+ end
35
+
36
+ def to_node_map(method_source)
37
+ @step_list.each_with_object(@name => make_track_entry_node) do |step, node_map|
38
+ node_map[step.name] = step.to_node(method_source)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def make_track_entry_node
45
+ MutationStep.new(
46
+ name: @name,
47
+ body: proc { true },
48
+ router_def: TRACK_ENTRY_ROUTER_DEF,
49
+ next_step: first_step_name
50
+ ).to_node(nil)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,51 @@
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(step)
22
+ @tracks[@current_track].add_step(step)
23
+ end
24
+
25
+ def first_step_name
26
+ @tracks[:main].first_step_name
27
+ end
28
+
29
+ def main_track_empty?
30
+ @tracks[:main].empty?
31
+ end
32
+
33
+ def to_node_map(method_source)
34
+ @tracks.reduce({}) do |node_map, (_, track)|
35
+ node_map.merge!(
36
+ track.to_node_map(method_source)
37
+ )
38
+ end
39
+ end
40
+
41
+ def to_flow(method_source)
42
+ raise NoStepsError, method_source if main_track_empty?
43
+
44
+ Flows::Flow.new(
45
+ start_node: first_step_name,
46
+ node_map: to_node_map(method_source)
47
+ )
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,73 @@
1
+ module Flows
2
+ class SharedContextPipeline
3
+ # @api private
4
+ class Wrap
5
+ attr_reader :router_def, :tracks_definitions
6
+
7
+ # :reek:Attribute
8
+ attr_accessor :next_step
9
+
10
+ EMPTY_HASH = {}.freeze
11
+
12
+ NODE_PREPROCESSOR = lambda do |_input, context, _node_meta|
13
+ [[context], EMPTY_HASH]
14
+ end
15
+
16
+ NODE_POSTPROCESSOR = lambda do |result, context, _node_meta|
17
+ context[:data].merge!(result.instance_variable_get(:@data))
18
+
19
+ result
20
+ end
21
+
22
+ def initialize(method_name:, router_def:, &tracks_definitions)
23
+ @method_name = method_name
24
+ @router_def = router_def
25
+ @tracks_definitions = tracks_definitions
26
+
27
+ singleton_class.extend DSL::Tracks
28
+ singleton_class.extend Result::Helpers
29
+
30
+ singleton_class.instance_exec(&tracks_definitions)
31
+ end
32
+
33
+ # on `#dup` we're getting new empty singleton class
34
+ # so we need to initialize it like original one
35
+ def initialize_dup(other)
36
+ singleton_class.extend DSL::Tracks
37
+ singleton_class.extend Result::Helpers
38
+ singleton_class.instance_exec(&other.tracks_definitions)
39
+ end
40
+
41
+ def name
42
+ singleton_class.tracks.first_step_name
43
+ end
44
+
45
+ def to_node(method_source)
46
+ Flows::Flow::Node.new(
47
+ body: make_body(method_source),
48
+ router: router_def.to_router(next_step),
49
+ meta: { wrap_name: @method_name },
50
+ preprocessor: NODE_PREPROCESSOR,
51
+ postprocessor: NODE_POSTPROCESSOR
52
+ )
53
+ end
54
+
55
+ private
56
+
57
+ def make_flow(method_source)
58
+ singleton_class.tracks.to_flow(method_source)
59
+ end
60
+
61
+ def make_body(method_source)
62
+ flow = make_flow(method_source)
63
+ wrapper = method_source.method(@method_name)
64
+
65
+ lambda do |context|
66
+ wrapper.call(context[:data], context[:meta]) do
67
+ flow.call(nil, context: context)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ 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,86 @@
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
+ # SingletonVarsSetup = Flows::Util::InheritableSingletonVars::SomeStrategy.make_module(
45
+ # **options_here
46
+ # )
47
+ #
48
+ # include SingeltonVarsSetup # extend also will work
49
+ # end
50
+ #
51
+ # In case of extensions and mixins:
52
+ #
53
+ # module MyExtension
54
+ # SingletonVarsSetup = Flows::Util::InheritableSingletonVars::SomeStrategy.make_module(
55
+ # **options_here
56
+ # )
57
+ #
58
+ # include SingeltonVarsSetup # extend also will work
59
+ # end
60
+ #
61
+ # class SomethingA
62
+ # extend MyExtension
63
+ # end
64
+ #
65
+ # module MyMixin
66
+ # SingletonVarsSetup = Flows::Util::InheritableSingletonVars::SomeStrategy.make_module(
67
+ # **options_here
68
+ # )
69
+ #
70
+ # include SingeltonVarsSetup # extend also will work
71
+ # end
72
+ #
73
+ # class SomethingB
74
+ # include MyMixin
75
+ # end
76
+ #
77
+ # Moreover, you can use multiple strategies in the same class.
78
+ #
79
+ # @since 0.4.0
80
+ module InheritableSingletonVars
81
+ end
82
+ end
83
+ end
84
+
85
+ require_relative './inheritable_singleton_vars/dup_strategy'
86
+ require_relative './inheritable_singleton_vars/isolation_strategy'