trailblazer-activity-dsl-linear 0.5.0 → 1.0.0.beta1
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/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
|