flows 0.2.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|