flows 0.2.0 → 0.6.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.
- checksums.yaml +4 -4
- data/.github/workflows/{build.yml → test.yml} +5 -10
- data/.gitignore +9 -1
- data/.mdlrc +1 -1
- data/.reek.yml +54 -0
- data/.rubocop.yml +26 -7
- data/.rubocop_todo.yml +27 -0
- data/.ruby-version +1 -1
- data/.yardopts +1 -0
- data/CHANGELOG.md +81 -0
- data/Gemfile +0 -6
- data/README.md +167 -363
- data/Rakefile +35 -1
- data/bin/.rubocop.yml +5 -0
- data/bin/all_the_errors +55 -0
- data/bin/benchmark +73 -105
- data/bin/benchmark_cli/compare.rb +118 -0
- data/bin/benchmark_cli/compare/a_plus_b.rb +22 -0
- data/bin/benchmark_cli/compare/base.rb +45 -0
- data/bin/benchmark_cli/compare/command.rb +47 -0
- data/bin/benchmark_cli/compare/ten_steps.rb +22 -0
- data/bin/benchmark_cli/examples.rb +23 -0
- data/bin/benchmark_cli/examples/.rubocop.yml +22 -0
- data/bin/benchmark_cli/examples/a_plus_b/dry_do.rb +23 -0
- data/bin/benchmark_cli/examples/a_plus_b/dry_transaction.rb +17 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_do.rb +22 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_railway.rb +13 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_scp.rb +13 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_scp_mut.rb +13 -0
- data/bin/benchmark_cli/examples/a_plus_b/flows_scp_oc.rb +21 -0
- data/bin/benchmark_cli/examples/a_plus_b/trailblazer.rb +15 -0
- data/bin/benchmark_cli/examples/ten_steps/dry_do.rb +70 -0
- data/bin/benchmark_cli/examples/ten_steps/dry_transaction.rb +64 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_do.rb +69 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_railway.rb +58 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_scp.rb +58 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_scp_mut.rb +58 -0
- data/bin/benchmark_cli/examples/ten_steps/flows_scp_oc.rb +66 -0
- data/bin/benchmark_cli/examples/ten_steps/trailblazer.rb +60 -0
- data/bin/benchmark_cli/helpers.rb +12 -0
- data/bin/benchmark_cli/ruby.rb +15 -0
- data/bin/benchmark_cli/ruby/command.rb +38 -0
- data/bin/benchmark_cli/ruby/method_exec.rb +71 -0
- data/bin/benchmark_cli/ruby/self_class.rb +69 -0
- data/bin/benchmark_cli/ruby/structs.rb +90 -0
- data/bin/console +1 -0
- data/bin/docserver +7 -0
- data/bin/errors +138 -0
- data/bin/errors_cli/contract_error_demo.rb +49 -0
- data/bin/errors_cli/di_error_demo.rb +38 -0
- data/bin/errors_cli/flow_error_demo.rb +22 -0
- data/bin/errors_cli/flows_router_error_demo.rb +15 -0
- data/bin/errors_cli/interface_error_demo.rb +17 -0
- data/bin/errors_cli/oc_error_demo.rb +40 -0
- data/bin/errors_cli/railway_error_demo.rb +10 -0
- data/bin/errors_cli/result_error_demo.rb +13 -0
- data/bin/errors_cli/scp_error_demo.rb +17 -0
- data/docs/README.md +3 -187
- data/docs/_sidebar.md +0 -24
- data/docs/index.html +1 -1
- data/flows.gemspec +27 -2
- data/forspell.dict +9 -0
- data/lefthook.yml +9 -0
- data/lib/flows.rb +11 -5
- data/lib/flows/contract.rb +402 -0
- data/lib/flows/contract/array.rb +55 -0
- data/lib/flows/contract/case_eq.rb +43 -0
- data/lib/flows/contract/compose.rb +77 -0
- data/lib/flows/contract/either.rb +53 -0
- data/lib/flows/contract/error.rb +24 -0
- data/lib/flows/contract/hash.rb +75 -0
- data/lib/flows/contract/hash_of.rb +70 -0
- data/lib/flows/contract/helpers.rb +22 -0
- data/lib/flows/contract/predicate.rb +34 -0
- data/lib/flows/contract/transformer.rb +50 -0
- data/lib/flows/contract/tuple.rb +70 -0
- data/lib/flows/flow.rb +96 -7
- data/lib/flows/flow/errors.rb +29 -0
- data/lib/flows/flow/node.rb +132 -0
- data/lib/flows/flow/router.rb +29 -0
- data/lib/flows/flow/router/custom.rb +59 -0
- data/lib/flows/flow/router/errors.rb +11 -0
- data/lib/flows/flow/router/simple.rb +25 -0
- data/lib/flows/plugin.rb +15 -0
- data/lib/flows/plugin/dependency_injector.rb +170 -0
- data/lib/flows/plugin/dependency_injector/dependency.rb +24 -0
- data/lib/flows/plugin/dependency_injector/dependency_definition.rb +16 -0
- data/lib/flows/plugin/dependency_injector/dependency_list.rb +55 -0
- data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
- data/lib/flows/plugin/implicit_init.rb +45 -0
- data/lib/flows/plugin/interface.rb +84 -0
- data/lib/flows/plugin/output_contract.rb +85 -0
- data/lib/flows/plugin/output_contract/dsl.rb +48 -0
- data/lib/flows/plugin/output_contract/errors.rb +74 -0
- data/lib/flows/plugin/output_contract/wrapper.rb +55 -0
- data/lib/flows/plugin/profiler.rb +114 -0
- data/lib/flows/plugin/profiler/injector.rb +35 -0
- data/lib/flows/plugin/profiler/report.rb +48 -0
- data/lib/flows/plugin/profiler/report/events.rb +43 -0
- data/lib/flows/plugin/profiler/report/flat.rb +41 -0
- data/lib/flows/plugin/profiler/report/flat/method_report.rb +80 -0
- data/lib/flows/plugin/profiler/report/raw.rb +15 -0
- data/lib/flows/plugin/profiler/report/tree.rb +98 -0
- data/lib/flows/plugin/profiler/report/tree/calculated_node.rb +116 -0
- data/lib/flows/plugin/profiler/report/tree/node.rb +34 -0
- data/lib/flows/plugin/profiler/wrapper.rb +53 -0
- data/lib/flows/railway.rb +140 -34
- data/lib/flows/railway/dsl.rb +8 -18
- data/lib/flows/railway/errors.rb +8 -12
- data/lib/flows/railway/step.rb +24 -0
- data/lib/flows/railway/step_list.rb +38 -0
- data/lib/flows/result.rb +188 -2
- data/lib/flows/result/do.rb +158 -16
- data/lib/flows/result/err.rb +12 -6
- data/lib/flows/result/errors.rb +29 -17
- data/lib/flows/result/helpers.rb +25 -3
- data/lib/flows/result/ok.rb +12 -6
- data/lib/flows/shared_context_pipeline.rb +342 -0
- data/lib/flows/shared_context_pipeline/dsl.rb +12 -0
- data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +35 -0
- data/lib/flows/shared_context_pipeline/dsl/tracks.rb +52 -0
- data/lib/flows/shared_context_pipeline/errors.rb +17 -0
- data/lib/flows/shared_context_pipeline/mutation_step.rb +30 -0
- data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
- data/lib/flows/shared_context_pipeline/step.rb +55 -0
- data/lib/flows/shared_context_pipeline/track.rb +54 -0
- data/lib/flows/shared_context_pipeline/track_list.rb +51 -0
- data/lib/flows/shared_context_pipeline/wrap.rb +73 -0
- data/lib/flows/util.rb +17 -0
- data/lib/flows/util/inheritable_singleton_vars.rb +86 -0
- data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +100 -0
- data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +91 -0
- data/lib/flows/util/prepend_to_class.rb +191 -0
- data/lib/flows/version.rb +1 -1
- metadata +253 -38
- data/Gemfile.lock +0 -174
- data/bin/demo +0 -66
- data/bin/examples.rb +0 -195
- data/bin/profile_10steps +0 -106
- data/bin/ruby_benchmarks +0 -26
- data/docs/CNAME +0 -1
- data/docs/contributing/benchmarks_profiling.md +0 -3
- data/docs/contributing/local_development.md +0 -3
- data/docs/flow/direct_usage.md +0 -3
- data/docs/flow/general_idea.md +0 -3
- data/docs/operation/basic_usage.md +0 -1
- data/docs/operation/inject_steps.md +0 -3
- data/docs/operation/lambda_steps.md +0 -3
- data/docs/operation/result_shapes.md +0 -3
- data/docs/operation/routing_tracks.md +0 -3
- data/docs/operation/wrapping_steps.md +0 -3
- data/docs/overview/performance.md +0 -336
- data/docs/railway/basic_usage.md +0 -232
- data/docs/result_objects/basic_usage.md +0 -196
- data/docs/result_objects/do_notation.md +0 -139
- data/lib/flows/node.rb +0 -27
- data/lib/flows/operation.rb +0 -52
- data/lib/flows/operation/builder.rb +0 -130
- data/lib/flows/operation/builder/build_router.rb +0 -37
- data/lib/flows/operation/dsl.rb +0 -93
- data/lib/flows/operation/errors.rb +0 -75
- data/lib/flows/operation/executor.rb +0 -78
- data/lib/flows/railway/builder.rb +0 -68
- data/lib/flows/railway/executor.rb +0 -23
- data/lib/flows/result_router.rb +0 -14
- data/lib/flows/router.rb +0 -22
@@ -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
|
data/lib/flows/util.rb
ADDED
@@ -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'
|