trailblazer-activity-dsl-linear 0.5.0 → 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/CHANGES.md +52 -0
- data/Gemfile +3 -1
- data/README.md +9 -17
- data/lib/trailblazer/activity/dsl/linear/feature/merge.rb +36 -0
- data/lib/trailblazer/activity/dsl/linear/feature/patch.rb +40 -0
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/dsl.rb +281 -0
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping/inherit.rb +38 -0
- data/lib/trailblazer/activity/dsl/linear/feature/variable_mapping.rb +298 -0
- data/lib/trailblazer/activity/dsl/linear/helper/path.rb +106 -0
- data/lib/trailblazer/activity/dsl/linear/helper.rb +54 -128
- data/lib/trailblazer/activity/dsl/linear/normalizer/terminus.rb +92 -0
- data/lib/trailblazer/activity/dsl/linear/normalizer.rb +194 -77
- data/lib/trailblazer/activity/dsl/linear/sequence/builder.rb +47 -0
- data/lib/trailblazer/activity/dsl/linear/sequence/compiler.rb +72 -0
- data/lib/trailblazer/activity/dsl/linear/sequence/search.rb +58 -0
- data/lib/trailblazer/activity/dsl/linear/sequence.rb +34 -0
- data/lib/trailblazer/activity/dsl/linear/strategy.rb +116 -71
- data/lib/trailblazer/activity/dsl/linear/version.rb +1 -1
- data/lib/trailblazer/activity/dsl/linear.rb +21 -177
- data/lib/trailblazer/activity/fast_track.rb +42 -53
- data/lib/trailblazer/activity/path.rb +52 -127
- data/lib/trailblazer/activity/railway.rb +48 -68
- data/trailblazer-activity-dsl-linear.gemspec +3 -2
- metadata +41 -13
- data/lib/trailblazer/activity/dsl/linear/compiler.rb +0 -70
- data/lib/trailblazer/activity/dsl/linear/state.rb +0 -63
- data/lib/trailblazer/activity/dsl/linear/variable_mapping.rb +0 -240
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40f3c629e47f86039e0028f0e82f78a51f9b4d7d9e6c953a3605510b1241c050
|
4
|
+
data.tar.gz: 54a79c00d4a4505c0da36dcffb209f1fdf6a5c438156620c665443d481039f7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c4c2d3df3fff7dbae401d840c1a4efca1397be7a7839b2d89b05300bd17f4b41df0f86c32c19ea98c2a61e8d1a045278592041605c06c7e4a46797e8f5bc97b7
|
7
|
+
data.tar.gz: daa3b6f02156a479bda2b6359583be835a6b908415a388714c6e3d17004e6dff3f8682619e83ace151259deca834744de7521d30916626df05a9e207b2705e7e
|
data/.github/workflows/ci.yml
CHANGED
@@ -6,7 +6,7 @@ jobs:
|
|
6
6
|
fail-fast: false
|
7
7
|
matrix:
|
8
8
|
# Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
|
9
|
-
ruby: [2.5, 2.6, 2.7, '3.0', head, jruby
|
9
|
+
ruby: [2.5, 2.6, 2.7, '3.0', head, jruby]
|
10
10
|
runs-on: ubuntu-latest
|
11
11
|
steps:
|
12
12
|
- uses: actions/checkout@v2
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,55 @@
|
|
1
|
+
# 1.0.0
|
2
|
+
|
3
|
+
## Additions
|
4
|
+
|
5
|
+
* Introduce composable input/output filters with `In()`, `Out()` and `Inject()`. # FIXME: add link
|
6
|
+
* We no longer store arbitrary variables from `#step` calls in the sequence row's `data` field.
|
7
|
+
Use the `DataVariable` helper to mark variables for storage in `data`.
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
step :find_model,
|
11
|
+
model_class: Song,
|
12
|
+
Trailblazer::Activity::DSL::Linear::Helper.DataVariable() => :model_class
|
13
|
+
```
|
14
|
+
* Add `Normalizer.extend!` to add steps to a particular normalizer. # FIXME: add link
|
15
|
+
* Add `Strategy.terminus` to add termini. # FIXME: add link
|
16
|
+
* The `Sequence` instance is now readable via `#to_h`: `Strategy.to_h[:sequence]`.
|
17
|
+
* In Normalizer, the `path.wirings` step is now named `activity.wirings`.
|
18
|
+
|
19
|
+
## Design
|
20
|
+
|
21
|
+
* DSL logic: move as much as possible into the normalizer as it's much easier to understand and follow (and debug).
|
22
|
+
* Each DSL method now directly invokes a normalizer pipeline that processes the user options and produces an ADDS structure.
|
23
|
+
* We now need `Sequence::Row` instances in `Sequence` to adhere to the Adds specification.
|
24
|
+
* Rename `Linear::State` to `Linear::Sequence::Builder`. This is now a stateless function, only.
|
25
|
+
Sequence::Builder.()
|
26
|
+
* @state ?
|
27
|
+
* Remove `Strategy@activity` instance variable and move it to `@state[:activity]`.
|
28
|
+
* Much better file structuring.
|
29
|
+
|
30
|
+
## Internals
|
31
|
+
|
32
|
+
* Use `Trailblazer::Declarative::State` to maintain sequence and other fields. This makes inheritance consistent.
|
33
|
+
* Make `Strategy` a class. It makes constant management much simpler to understand.
|
34
|
+
* `Linear.end_id` now accepts keyword arguments (mainly, `:semantic`).
|
35
|
+
* `Strategy.apply_step_on_state!` is now an immutable `Sequence::Builder.update_sequence_for`.
|
36
|
+
* The `Railway.Path()` helper returns a `DSL::PathBranch` non-symbol that is then picked up and processed by the normalizer (exactly how we do it with `In()`, `Track()` etc.). Branching implementation is handled in `helper/path.rb`.
|
37
|
+
* Remove `State.update_options`. Use `@state.update!`.
|
38
|
+
* Remove `Helper.normalize`.
|
39
|
+
* Remove `Linear::DSL.insert_task`. The canonical way to add steps is using the ADDS interface going through a normalizer.
|
40
|
+
That's why there's a normalizer for `end` (or "terminus") now for consistency.
|
41
|
+
* Remove `Helper::ClassMethods`, `Helper` is now the namespace to mix in your own functions (and ours, like `Output()`).
|
42
|
+
* Introduce `Helper::Constants` for namespaced macros such as `Policy::Pundit()`.
|
43
|
+
|
44
|
+
## Renaming
|
45
|
+
|
46
|
+
* Rename `Linear::State::Normalizer` to `Linear::Normalizer::Normalizers` as it represents a container for normalizers.
|
47
|
+
* Move `Linear::Insert` to `Activity::Adds::Insert` in the `trailblazer-activity` gem.
|
48
|
+
* Move `Linear::Search` to `Linear::Sequence::Search` and `Linear::Compiler` to `Linear::Sequence::Compiler`.
|
49
|
+
* `TaskWrap::Pipeline.prepend` is now `Linear::Normalizer.prepend_to`. To use the `:replace` option you can use `Linear::Normalizer.replace`.
|
50
|
+
* Move `Sequence::IndexError` to `Activity::Adds::IndexError` in the `trailblazer-activity` gem. Remove `IndexError#step_id`.
|
51
|
+
* Move DSL structures like `OutputSemantic` to `Linear` namespace.
|
52
|
+
|
1
53
|
# 0.5.0
|
2
54
|
|
3
55
|
* Introduce `:inject` option to pass-through injected variables and to default input variables.
|
data/Gemfile
CHANGED
@@ -8,6 +8,8 @@ gem "minitest-line"
|
|
8
8
|
gem "rubocop", require: false
|
9
9
|
|
10
10
|
# gem "trailblazer-developer", path: "../trailblazer-developer"
|
11
|
+
# gem "trailblazer-developer", github: "trailblazer/trailblazer-developer"
|
12
|
+
# gem "trailblazer-declarative", path: "../trailblazer-declarative"
|
11
13
|
# gem "trailblazer-activity", path: "../trailblazer-activity"
|
12
|
-
gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
|
14
|
+
# gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
|
13
15
|
# gem "trailblazer-activity", path: "../circuit"
|
data/README.md
CHANGED
@@ -1,25 +1,24 @@
|
|
1
1
|
# Activity-DSL-Linear
|
2
2
|
|
3
|
-
The `activity-dsl-linear` gem brings
|
4
|
-
- [Path](https://trailblazer.to/2.1/docs/activity.html#activity-strategy-path)
|
5
|
-
- [Railway](https://trailblazer.to/2.1/docs/activity.html#activity-strategy-railway)
|
6
|
-
- [Fasttrack](https://trailblazer.to/2.1/docs/activity.html#activity-strategy-fasttrack)
|
7
|
-
|
8
|
-
DSLs strategies for buildig activities. It is build around [`activity`](https://github.com/trailblazer/trailblazer-activity) gem.
|
3
|
+
The `trailblazer-activity-dsl-linear` gem brings the popular `step` DSL around the [`activity`](https://github.com/trailblazer/trailblazer-activity) gem. It allows to create classes that you might mostly know as _operations_ - service objects that execute your business logic in a certain order, depending on how you harness the `step` DSL.
|
9
4
|
|
10
5
|
Please find the [full documentation on the Trailblazer website](https://trailblazer.to/2.1/docs/activity.html#activity-strategy).
|
11
6
|
|
12
7
|
## Example
|
13
8
|
|
14
|
-
The `activity-dsl-linear` gem provides three default patterns to model
|
9
|
+
The `activity-dsl-linear` gem provides three default patterns to model activities: `Path`, `Railway` and `FastTrack`. Here's an example of what a railway activity could look like, along with some more complex connections (you can read more about Railway strategy in the [docs](https://trailblazer.to/2.1/docs/activity.html#activity-strategy-railway)).
|
15
10
|
|
16
11
|
```ruby
|
17
|
-
require "trailblazer-activity"
|
18
12
|
require "trailblazer-activity-dsl-linear"
|
19
13
|
|
20
14
|
class Memo::Update < Trailblazer::Activity::Railway
|
21
|
-
#
|
22
|
-
|
15
|
+
# Use the DSL to describe the layout of the activity.
|
16
|
+
step :find_model
|
17
|
+
step :validate, Output(:failure) => End(:validation_error)
|
18
|
+
step :save
|
19
|
+
fail :log_error
|
20
|
+
|
21
|
+
# Here comes your business logic.
|
23
22
|
def find_model(ctx, id:, **)
|
24
23
|
ctx[:model] = Memo.find_by(id: id)
|
25
24
|
end
|
@@ -37,13 +36,6 @@ class Memo::Update < Trailblazer::Activity::Railway
|
|
37
36
|
def log_error(ctx, params:, **)
|
38
37
|
ctx[:log] = "Some idiot wrote #{params.inspect}"
|
39
38
|
end
|
40
|
-
|
41
|
-
# here comes the DSL describing the layout of the activity
|
42
|
-
#
|
43
|
-
step :find_model
|
44
|
-
step :validate, Output(:failure) => End(:validation_error)
|
45
|
-
step :save
|
46
|
-
fail :log_error
|
47
39
|
end
|
48
40
|
```
|
49
41
|
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Trailblazer::Activity
|
2
|
+
module DSL
|
3
|
+
module Linear
|
4
|
+
module Merge
|
5
|
+
# Class methods for {Strategy}.
|
6
|
+
module DSL
|
7
|
+
def merge!(activity)
|
8
|
+
old_seq = to_h[:sequence]
|
9
|
+
new_seq = activity.to_h[:sequence]
|
10
|
+
|
11
|
+
seq = Merge.call(old_seq, new_seq, end_id: "End.success")
|
12
|
+
|
13
|
+
# Update the DSL's sequence, then recompile the actual activity.
|
14
|
+
recompile!(seq)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Compile-time logic to merge two activities.
|
19
|
+
def self.call(old_seq, new_seq, end_id: "End.success") # DISCUSS: also Insert
|
20
|
+
new_seq = strip_start_and_ends(new_seq, end_id: end_id)
|
21
|
+
|
22
|
+
_seq = Adds.apply_adds(
|
23
|
+
old_seq,
|
24
|
+
new_seq.collect { |row| {insert: [Adds::Insert.method(:Prepend), end_id], row: row } }
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.strip_start_and_ends(seq, end_id:)
|
29
|
+
cut_off_index = end_id.nil? ? seq.size : Adds::Insert.find_index(seq, end_id) # find the "first" end.
|
30
|
+
|
31
|
+
seq[1..cut_off_index-1]
|
32
|
+
end
|
33
|
+
end # Merge
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Trailblazer::Activity
|
2
|
+
module DSL
|
3
|
+
module Linear
|
4
|
+
module Patch
|
5
|
+
# DISCUSS: we could make this a generic DSL option, not just for Subprocess().
|
6
|
+
# Currently, this is called from the Subprocess() helper.
|
7
|
+
def self.customize(activity, options:)
|
8
|
+
options = options.is_a?(Proc) ?
|
9
|
+
{ [] => options } : # hash-wrapping with empty path, for patching given activity itself
|
10
|
+
options
|
11
|
+
|
12
|
+
options.each do |path, patch|
|
13
|
+
activity = call(activity, path, patch) # TODO: test if multiple patches works!
|
14
|
+
end
|
15
|
+
|
16
|
+
activity
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.call(activity, path, customization)
|
20
|
+
task_id, *path = path
|
21
|
+
|
22
|
+
patch =
|
23
|
+
if task_id
|
24
|
+
segment_activity = Introspect::Graph(activity).find(task_id).task
|
25
|
+
patched_segment_activity = call(segment_activity, path, customization)
|
26
|
+
|
27
|
+
# Replace the patched subprocess.
|
28
|
+
-> { step Subprocess(patched_segment_activity), inherit: true, replace: task_id, id: task_id }
|
29
|
+
else
|
30
|
+
customization # apply the *actual* patch from the Subprocess() call.
|
31
|
+
end
|
32
|
+
|
33
|
+
patched_activity = Class.new(activity)
|
34
|
+
patched_activity.class_exec(&patch)
|
35
|
+
patched_activity
|
36
|
+
end
|
37
|
+
end # Patch
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
module DSL
|
4
|
+
module Linear
|
5
|
+
module VariableMapping
|
6
|
+
# Code invoked through the normalizer, building runtime structures.
|
7
|
+
# Naming
|
8
|
+
# Option: Tuple => user filter
|
9
|
+
# Tuple: #<In ...>
|
10
|
+
module DSL
|
11
|
+
module_function
|
12
|
+
|
13
|
+
# Compute pipeline for {:input} option.
|
14
|
+
def pipe_for_mono_input(input: [], inject: [], in_filters: [], output: [], **)
|
15
|
+
has_input = Array(input).any?
|
16
|
+
has_mono_options = has_input || Array(inject).any? || Array(output).any? # :input, :inject and :output are "mono options".
|
17
|
+
has_composable_options = in_filters.any? # DISCUSS: why are we not testing Inject()?
|
18
|
+
|
19
|
+
if has_mono_options && has_composable_options
|
20
|
+
warn "[Trailblazer] You are mixing `:input` and `In() => ...`. `In()` and Inject () options are ignored and `:input` wins: #{input} #{inject} #{output} <> #{in_filters} / "
|
21
|
+
end
|
22
|
+
|
23
|
+
pipeline = initial_input_pipeline(add_default_ctx: !has_input)
|
24
|
+
pipeline = add_steps_for_input_option(pipeline, input: input)
|
25
|
+
pipeline = add_steps_for_inject_option(pipeline, inject: inject)
|
26
|
+
|
27
|
+
return pipeline, has_mono_options, has_composable_options
|
28
|
+
end
|
29
|
+
|
30
|
+
# Compute pipeline for In() and Inject().
|
31
|
+
# We allow to inject {:initial_input_pipeline} here in order to skip creating a new input pipeline and instead
|
32
|
+
# use the inherit one.
|
33
|
+
def pipe_for_composable_input(in_filters: [], inject_filters: [], initial_input_pipeline: initial_input_pipeline_for(in_filters), **)
|
34
|
+
inject_filters = DSL::Inject.filters_for_injects(inject_filters) # {Inject() => ...} the pure user input gets translated into AddVariable aggregate steps.
|
35
|
+
in_filters = DSL::Tuple.filters_from_options(in_filters)
|
36
|
+
|
37
|
+
# With only injections defined, we do not filter out anything, we use the original ctx
|
38
|
+
# and _add_ defaulting for injected variables.
|
39
|
+
pipeline = add_filter_steps(initial_input_pipeline, in_filters)
|
40
|
+
pipeline = add_filter_steps(pipeline, inject_filters)
|
41
|
+
end
|
42
|
+
|
43
|
+
# initial pipleline depending on whether or not we got any In() filters.
|
44
|
+
def initial_input_pipeline_for(in_filters)
|
45
|
+
is_inject_only = Array(in_filters).empty?
|
46
|
+
|
47
|
+
initial_input_pipeline(add_default_ctx: is_inject_only)
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# Adds the default_ctx step as per option {:add_default_ctx}
|
52
|
+
def initial_input_pipeline(add_default_ctx: false)
|
53
|
+
# No In() or {:input}. Use default ctx, which is the original ctxx.
|
54
|
+
# When using Inject without In/:input, we also need a {default_input} ctx.
|
55
|
+
default_ctx_row =
|
56
|
+
add_default_ctx ? Activity::TaskWrap::Pipeline.Row(*default_input_ctx_config) : nil
|
57
|
+
|
58
|
+
pipe = Activity::TaskWrap::Pipeline.new(
|
59
|
+
[
|
60
|
+
Activity::TaskWrap::Pipeline.Row("input.init_hash", VariableMapping.method(:initial_aggregate)), # very first step
|
61
|
+
default_ctx_row,
|
62
|
+
Activity::TaskWrap::Pipeline.Row("input.scope", VariableMapping.method(:scope)), # last step
|
63
|
+
].compact
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
def default_input_ctx_config # almost a Row.
|
68
|
+
["input.default_input", VariableMapping.method(:default_input_ctx)]
|
69
|
+
end
|
70
|
+
|
71
|
+
# Handle {:input} and {:inject} option, the "old" interface.
|
72
|
+
def add_steps_for_input_option(pipeline, input:)
|
73
|
+
tuple = DSL.In(name: ":input") # simulate {In() => input}
|
74
|
+
input_filter = DSL::Tuple.filters_from_options([[tuple, input]])
|
75
|
+
|
76
|
+
add_filter_steps(pipeline, input_filter)
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def pipe_for_mono_output(output_with_outer_ctx: false, output: [], out_filters: [], **)
|
81
|
+
# No Out(), no {:output} will result in a default_output_ctx step.
|
82
|
+
has_output = Array(output).any?
|
83
|
+
has_mono_options = has_output
|
84
|
+
has_composable_options = Array(out_filters).any?
|
85
|
+
|
86
|
+
if has_mono_options && has_composable_options
|
87
|
+
warn "[Trailblazer] You are mixing `:output` and `Out() => ...`. `Out()` options are ignored and `:output` wins."
|
88
|
+
end
|
89
|
+
|
90
|
+
pipeline = initial_output_pipeline(add_default_ctx: !has_output)
|
91
|
+
pipeline = add_steps_for_output_option(pipeline, output: output, output_with_outer_ctx: output_with_outer_ctx)
|
92
|
+
|
93
|
+
return pipeline, has_mono_options, has_composable_options
|
94
|
+
end
|
95
|
+
|
96
|
+
def add_steps_for_output_option(pipeline, output:, output_with_outer_ctx:)
|
97
|
+
tuple = DSL.Out(name: ":output", with_outer_ctx: output_with_outer_ctx) # simulate {Out() => output}
|
98
|
+
output_filter = DSL::Tuple.filters_from_options([[tuple, output]])
|
99
|
+
|
100
|
+
add_filter_steps(pipeline, output_filter, prepend_to: "output.merge_with_original")
|
101
|
+
end
|
102
|
+
|
103
|
+
def pipe_for_composable_output(out_filters: [], initial_output_pipeline: initial_output_pipeline(add_default_ctx: Array(out_filters).empty?), **)
|
104
|
+
out_filters = DSL::Tuple.filters_from_options(out_filters)
|
105
|
+
|
106
|
+
add_filter_steps(initial_output_pipeline, out_filters, prepend_to: "output.merge_with_original")
|
107
|
+
end
|
108
|
+
|
109
|
+
def initial_output_pipeline(add_default_ctx: false)
|
110
|
+
default_ctx_row =
|
111
|
+
add_default_ctx ? Activity::TaskWrap::Pipeline.Row(*default_output_ctx_config) : nil
|
112
|
+
|
113
|
+
Activity::TaskWrap::Pipeline.new(
|
114
|
+
[
|
115
|
+
Activity::TaskWrap::Pipeline.Row("output.init_hash", VariableMapping.method(:initial_aggregate)), # very first step
|
116
|
+
default_ctx_row,
|
117
|
+
Activity::TaskWrap::Pipeline.Row("output.merge_with_original", VariableMapping.method(:merge_with_original)), # last step
|
118
|
+
].compact
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
122
|
+
def default_output_ctx_config # almost a Row.
|
123
|
+
["output.default_output", VariableMapping.method(:default_output_ctx)]
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_steps_for_inject_option(pipeline, inject:)
|
127
|
+
injects = inject.collect { |name| name.is_a?(Symbol) ? [DSL.Inject(), [name]] : [DSL.Inject(), name] }
|
128
|
+
|
129
|
+
tuples = DSL::Inject.filters_for_injects(injects) # DISCUSS: should we add passthrough/defaulting here at Inject()-time?
|
130
|
+
|
131
|
+
add_filter_steps(pipeline, tuples)
|
132
|
+
end
|
133
|
+
|
134
|
+
def add_filter_steps(pipeline, rows, prepend_to: "input.scope") # FIXME: do we need all this?
|
135
|
+
rows = add_variables_steps_for_filters(rows)
|
136
|
+
|
137
|
+
adds = Activity::Adds::FriendlyInterface.adds_for(
|
138
|
+
rows.collect { |row| [row[1], id: row[0], prepend: prepend_to] }
|
139
|
+
)
|
140
|
+
|
141
|
+
Activity::Adds.apply_adds(pipeline, adds)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns array of step rows ("sequence").
|
145
|
+
# @param filters [Array] List of {Filter} objects
|
146
|
+
def add_variables_steps_for_filters(filters) # FIXME: allow output too!
|
147
|
+
filters.collect do |filter|
|
148
|
+
["input.add_variables.#{filter.name}", filter.aggregate_step] # FIXME: config name sucks, of course, if we want to allow inserting etc.
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
# Filter code
|
154
|
+
# Converting user options to callable filters.
|
155
|
+
|
156
|
+
# @param [Array, Hash, Proc] User option coming from the DSL, like {[:model]}
|
157
|
+
#
|
158
|
+
# Returns a "filter interface" callable that's invoked in {AddVariables}:
|
159
|
+
# filter.(new_ctx, ..., keyword_arguments: new_ctx.to_hash, **circuit_options)
|
160
|
+
def self.build_filter(user_filter)
|
161
|
+
Trailblazer::Option(filter_for(user_filter))
|
162
|
+
end
|
163
|
+
|
164
|
+
# Convert a user option such as {[:model]} to a filter.
|
165
|
+
#
|
166
|
+
# Returns a filter proc to be called in an Option.
|
167
|
+
# @private
|
168
|
+
def self.filter_for(filter)
|
169
|
+
if filter.is_a?(::Array) || filter.is_a?(::Hash)
|
170
|
+
filter_from_dsl(filter)
|
171
|
+
else
|
172
|
+
filter
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# The returned filter compiles a new hash for Scoped/Unscoped that only contains
|
177
|
+
# the desired i/o variables.
|
178
|
+
#
|
179
|
+
# Filter expects a "filter interface" {(ctx, **)}.
|
180
|
+
def self.filter_from_dsl(map)
|
181
|
+
hsh = DSL.hash_for(map)
|
182
|
+
|
183
|
+
->(incoming_ctx, **kwargs) { Hash[hsh.collect { |from_name, to_name| [to_name, incoming_ctx[from_name]] }] }
|
184
|
+
end
|
185
|
+
|
186
|
+
def self.hash_for(ary)
|
187
|
+
return ary if ary.instance_of?(::Hash)
|
188
|
+
Hash[ary.collect { |name| [name, name] }]
|
189
|
+
end
|
190
|
+
|
191
|
+
# Keeps user's DSL configuration for a particular io-pipe step.
|
192
|
+
# Implements the interface for the actual I/O code and is DSL code happening in the normalizer.
|
193
|
+
# The actual I/O code expects {DSL::In} and {DSL::Out} objects to generate the two io-pipes.
|
194
|
+
#
|
195
|
+
# If a user needs to inject their own private iop step they can create this data structure with desired values here.
|
196
|
+
# This is also the reason why a lot of options computation such as {:with_outer_ctx} happens here and not in the IO code.
|
197
|
+
|
198
|
+
class Tuple < Struct.new(:name, :add_variables_class, :filter_builder, :insert_args)
|
199
|
+
def self.filters_from_options(tuples_to_user_filters)
|
200
|
+
tuples_to_user_filters.collect { |tuple, user_filter| tuple.(user_filter) }
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
# @return [Filter] Filter instance that keeps {name} and {aggregate_step}.
|
205
|
+
def call(user_filter)
|
206
|
+
filter = filter_builder.(user_filter)
|
207
|
+
aggregate_step = add_variables_class.new(filter, user_filter)
|
208
|
+
|
209
|
+
VariableMapping::Filter.new(aggregate_step, filter, name, add_variables_class)
|
210
|
+
end
|
211
|
+
end # TODO: implement {:insert_args}
|
212
|
+
|
213
|
+
# In, Out and Inject are objects instantiated when using the DSL, for instance {In() => [:model]}.
|
214
|
+
class In < Tuple; end
|
215
|
+
class Out < Tuple; end
|
216
|
+
|
217
|
+
def self.In(name: rand, add_variables_class: AddVariables, filter_builder: method(:build_filter))
|
218
|
+
In.new(name, add_variables_class, filter_builder)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Builder for a DSL Output() object.
|
222
|
+
def self.Out(name: rand, add_variables_class: AddVariables::Output, with_outer_ctx: false, delete: false, filter_builder: method(:build_filter), read_from_aggregate: false)
|
223
|
+
add_variables_class = AddVariables::Output::WithOuterContext if with_outer_ctx
|
224
|
+
add_variables_class = AddVariables::Output::Delete if delete
|
225
|
+
filter_builder = ->(user_filter) { user_filter } if delete
|
226
|
+
add_variables_class = AddVariables::ReadFromAggregate if read_from_aggregate
|
227
|
+
|
228
|
+
Out.new(name, add_variables_class, filter_builder)
|
229
|
+
end
|
230
|
+
|
231
|
+
def self.Inject()
|
232
|
+
Inject.new
|
233
|
+
end
|
234
|
+
|
235
|
+
# This class is supposed to hold configuration options for Inject().
|
236
|
+
class Inject
|
237
|
+
# Translate the raw input of the user to {In} tuples
|
238
|
+
# @return Array of VariableMapping::Filter
|
239
|
+
def self.filters_for_injects(injects)
|
240
|
+
injects.collect do |inject, user_filter| # iterate all {Inject() => user_filter} calls
|
241
|
+
DSL::Inject.compute_filters_for_inject(inject, user_filter)
|
242
|
+
end.flatten(1)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Compute {In} tuples from the user's DSL input.
|
246
|
+
# We simply use AddVariables but use our own {inject_filter} which checks if the particular
|
247
|
+
# variable is already present in the incoming ctx.
|
248
|
+
def self.compute_filters_for_inject(inject, user_filter) # {user_filter} either [:current_user, :model] or {model: ->{}}
|
249
|
+
return filters_for_array(inject, user_filter) if user_filter.is_a?(Array)
|
250
|
+
filters_for_hash_of_callables(inject, user_filter)
|
251
|
+
end
|
252
|
+
|
253
|
+
# [:model, :current_user]
|
254
|
+
def self.filters_for_array(inject, user_filter)
|
255
|
+
user_filter.collect do |name|
|
256
|
+
inject_filter = ->(original_ctx, **) { original_ctx.key?(name) ? {name => original_ctx[name]} : {} } # FIXME: make me an {Inject::} method.
|
257
|
+
|
258
|
+
filter_for(inject, inject_filter, name, "passthrough")
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# {model: ->(*) { snippet }}
|
263
|
+
def self.filters_for_hash_of_callables(inject, user_filter)
|
264
|
+
user_filter.collect do |name, defaulting_filter|
|
265
|
+
inject_filter = ->(original_ctx, **kws) { original_ctx.key?(name) ? {name => original_ctx[name]} : {name => defaulting_filter.(original_ctx, **kws)} }
|
266
|
+
|
267
|
+
filter_for(inject, inject_filter, name, "defaulting_callable")
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def self.filter_for(inject, inject_filter, name, type)
|
272
|
+
DSL.In(name: "inject.#{type}.#{name.inspect}", add_variables_class: AddVariables).(inject_filter)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
end # DSL
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Trailblazer
|
2
|
+
class Activity
|
3
|
+
module DSL
|
4
|
+
module Linear
|
5
|
+
module VariableMapping
|
6
|
+
# Implements the {inherit: [:variable_mapping]} feature.
|
7
|
+
module Inherit
|
8
|
+
def self.extended(strategy) # FIXME: who implements {extend!}
|
9
|
+
Linear::Normalizer.extend!(strategy, :step) do |normalizer|
|
10
|
+
Linear::Normalizer.prepend_to(
|
11
|
+
normalizer,
|
12
|
+
"activity.normalize_input_output_filters",
|
13
|
+
{
|
14
|
+
"variable_mapping.inherit_option" => Linear::Normalizer.Task(VariableMapping::Inherit::Normalizer.method(:inherit_option)),
|
15
|
+
}
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Normalizer
|
21
|
+
# Inheriting the original I/O happens by grabbing the variable_mapping_pipelines
|
22
|
+
# from the original sequence and pass it on in the normalizer.
|
23
|
+
# It will eventually get processed by {VariableMapping#pipe_for_composable_input} etc.
|
24
|
+
def self.inherit_option(ctx, inherit: [], sequence:, id:, **)
|
25
|
+
return unless inherit.include?(:variable_mapping)
|
26
|
+
|
27
|
+
inherited_input_pipeline, inherited_output_pipeline = Linear::Normalizer::InheritOption.find_row(sequence, id).data[:variable_mapping_pipelines]
|
28
|
+
|
29
|
+
ctx[:initial_input_pipeline] = inherited_input_pipeline
|
30
|
+
ctx[:initial_output_pipeline] = inherited_output_pipeline
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end # VariableMapping
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|