trailblazer-activity-dsl-linear 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91482f9f8f3191ea94fccb525e24e55b95edb1f089ee24dd7748364af140476b
4
- data.tar.gz: f85598f3e6796e6df4aa6a3927654dc882dae26e2e8c39eff4ea2b4a86db98f7
3
+ metadata.gz: 87302630d03df0e0e40ad7e7b9902744a1b9d7586e157018eab7652c6ad7276c
4
+ data.tar.gz: 10a821885b6a9e4228a0e04545d28689d28f4d3470f74b5f597f6d9bc1b4dd50
5
5
  SHA512:
6
- metadata.gz: 88644ff777e10df4e7e6337742f4684e487bdac3e2899d5168f9f4769563171c68638608b2d3de495b66467d9046c2d3f2c12c94ebd4d046c9ec66bbb364cce6
7
- data.tar.gz: 53279e4799c2e08909e4a60e55a139c8455faf1d12b8f2fa78a5c80eef6446981e5468e0f962569be1538ce87a4567aef3ef271923be56e8cb17a83ae11610c9
6
+ metadata.gz: '08a071547063505433e4400c7f5e23c8203c72f125397bf71af8402050d728e1e5995fc71f5cd23a8da362c277bedb16a71e9d4f0b5f1c439d96eed82c4589fc'
7
+ data.tar.gz: 11e54d0175baa4fa338605faf1ac44ef1ef891a763890730c44119a239f11ea2f37497c80156bc2895a11b7b0a6ce58e9349e4a44e49f90c3dc48535ff179b57
@@ -1,3 +1,6 @@
1
+ ## This file is managed by Terraform.
2
+ ## Do not modify this file directly, as it may be overwritten.
3
+ ## Please open an issue instead.
1
4
  name: CI
2
5
  on: [push, pull_request]
3
6
  jobs:
@@ -5,13 +8,12 @@ jobs:
5
8
  strategy:
6
9
  fail-fast: false
7
10
  matrix:
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]
11
+ ruby: [2.7, '3.0', '3.1']
10
12
  runs-on: ubuntu-latest
11
13
  steps:
12
- - uses: actions/checkout@v2
14
+ - uses: actions/checkout@v3
13
15
  - uses: ruby/setup-ruby@v1
14
16
  with:
15
17
  ruby-version: ${{ matrix.ruby }}
16
- bundler-cache: true # runs 'bundle install' and caches installed gems automatically
18
+ bundler-cache: true
17
19
  - run: bundle exec rake
@@ -0,0 +1,19 @@
1
+ ## This file is managed by Terraform.
2
+ ## Do not modify this file directly, as it may be overwritten.
3
+ ## Please open an issue instead.
4
+ name: CI JRuby
5
+ on: [push, pull_request]
6
+ jobs:
7
+ test:
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [jruby, jruby-head]
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby }}
18
+ bundler-cache: true
19
+ - run: bundle exec rake
@@ -0,0 +1,19 @@
1
+ ## This file is managed by Terraform.
2
+ ## Do not modify this file directly, as it may be overwritten.
3
+ ## Please open an issue instead.
4
+ name: CI with EOL ruby versions
5
+ on: [push, pull_request]
6
+ jobs:
7
+ test:
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [2.5, 2.6]
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby }}
18
+ bundler-cache: true
19
+ - run: bundle exec rake
@@ -0,0 +1,19 @@
1
+ ## This file is managed by Terraform.
2
+ ## Do not modify this file directly, as it may be overwritten.
3
+ ## Please open an issue instead.
4
+ name: CI TruffleRuby
5
+ on: [push, pull_request]
6
+ jobs:
7
+ test:
8
+ strategy:
9
+ fail-fast: false
10
+ matrix:
11
+ ruby: [truffleruby, truffleruby-head]
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: ${{ matrix.ruby }}
18
+ bundler-cache: true
19
+ - run: bundle exec rake
data/CHANGES.md CHANGED
@@ -1,3 +1,31 @@
1
+ # 1.1.0
2
+
3
+ * Use `trailblazer-activity` 0.15.0.
4
+ * Remove `Path::DSL.OptionsForSequenceBuilder` and move concrete code to `Path::DSL.options_for_sequence_build`
5
+ which returns a set:
6
+
7
+ 1. default termini instructions for the concrete strategy
8
+ 2. options specific for this strategy subclass.
9
+
10
+ Everything else, such as merging user options, computing and adding termini, etc, now happens in
11
+ `Strategy::DSL.OptionsForSequenceBuilder`.
12
+ * Adding `Subprocess(Create, strict: true)` to wire all outputs of `Create` automatically.
13
+ Each output will be wired to its same named Track(semantic).
14
+ * Adding `Strategy(termini: )`
15
+ * For `output:` in combination with `:output_with_outer_ctx`, deprecate the second positional argument and make it
16
+ the `:outer_ctx` keyword argument instead.
17
+ * Introduce `Linear.Patch` as the public entry point for patching activities.
18
+ * Remove `Runtime.initial_aggregate` step for the input and output pipelines which results in slightly better runtime performance and less code.
19
+
20
+ ## Variable Mapping
21
+
22
+ * Simplify the architecture in `VariableMapping`, filters are now added directly into the `Pipeline`.
23
+ Performance increase from 17k to 25k from 1.0.0 to this version.
24
+ * Introduce `Inject(:variable)` to supersede the version receiving a big mapping hash.
25
+ * Add `Inject(:variable, override: true)` to always write a variable to ctx, regardless of its presence.
26
+ * Fix a bug where `Inject()` would override `In()` filters even though the latter was added latest. This
27
+ is fixed by treating both filter types equally and in the order they were added by the user (and the macro).
28
+
1
29
  # 1.0.0
2
30
 
3
31
  ## Additions
data/Gemfile CHANGED
@@ -5,11 +5,11 @@ gemspec
5
5
 
6
6
  gem "minitest-line"
7
7
 
8
- gem "rubocop", require: false
9
-
10
- gem "trailblazer-developer", path: "../trailblazer-developer"
8
+ # gem "trailblazer-developer", path: "../trailblazer-developer"
11
9
  # gem "trailblazer-developer", github: "trailblazer/trailblazer-developer"
12
10
  # gem "trailblazer-declarative", path: "../trailblazer-declarative"
13
11
  # gem "trailblazer-activity", path: "../trailblazer-activity"
14
12
  # gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
15
13
  # gem "trailblazer-activity", path: "../circuit"
14
+ gem "benchmark-ips"
15
+ # gem "trailblazer-core-utils", path: "../trailblazer-core-utils"
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require "rake/testtask"
4
4
  Rake::TestTask.new(:test) do |t|
5
5
  t.libs << "test"
6
6
  t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
7
+ t.test_files = FileList["test/**/*_test.rb"] - FileList["test/docs/autogenerated/*"]
8
8
  end
9
9
 
10
10
  task :default => :test
@@ -1,6 +1,10 @@
1
1
  class Trailblazer::Activity
2
2
  module DSL
3
3
  module Linear
4
+ def self.Patch(activity, instructions)
5
+ Patch.customize(activity, options: instructions)
6
+ end
7
+
4
8
  module Patch
5
9
  # DISCUSS: we could make this a generic DSL option, not just for Subprocess().
6
10
  # Currently, this is called from the Subprocess() helper.
@@ -21,7 +25,7 @@ class Trailblazer::Activity
21
25
 
22
26
  patch =
23
27
  if task_id
24
- segment_activity = Introspect::Graph(activity).find(task_id).task
28
+ segment_activity = Introspect::TaskMap(activity).find_by_id(task_id).task
25
29
  patched_segment_activity = call(segment_activity, path, customization)
26
30
 
27
31
  # Replace the patched subprocess.
@@ -10,39 +10,17 @@ module Trailblazer
10
10
  module DSL
11
11
  module_function
12
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
13
  # Compute pipeline for In() and Inject().
31
14
  # We allow to inject {:initial_input_pipeline} here in order to skip creating a new input pipeline and instead
32
15
  # 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, path_prefix: "inject")
16
+ def pipe_for_composable_input(in_filters: [], initial_input_pipeline: initial_input_pipeline_for(in_filters), **)
17
+ in_filters = DSL::Tuple.filters_from_options(in_filters)
18
+ pipeline = add_filter_steps(initial_input_pipeline, in_filters)
41
19
  end
42
20
 
43
21
  # initial pipleline depending on whether or not we got any In() filters.
44
22
  def initial_input_pipeline_for(in_filters)
45
- is_inject_only = Array(in_filters).empty?
23
+ is_inject_only = in_filters.find { |k, v| k.is_a?(VariableMapping::DSL::In) }.nil?
46
24
 
47
25
  initial_input_pipeline(add_default_ctx: is_inject_only)
48
26
  end
@@ -57,7 +35,6 @@ module Trailblazer
57
35
 
58
36
  pipe = Activity::TaskWrap::Pipeline.new(
59
37
  [
60
- Activity::TaskWrap::Pipeline.Row("input.init_hash", VariableMapping.method(:initial_aggregate)), # very first step
61
38
  default_ctx_row,
62
39
  Activity::TaskWrap::Pipeline.Row("input.scope", VariableMapping.method(:scope)), # last step
63
40
  ].compact
@@ -70,36 +47,12 @@ module Trailblazer
70
47
 
71
48
  # Handle {:input} and {:inject} option, the "old" interface.
72
49
  def add_steps_for_input_option(pipeline, input:)
73
- tuple = DSL.In(name: ":input") # simulate {In() => input}
50
+ tuple = DSL.In() # simulate {In() => input}
74
51
  input_filter = DSL::Tuple.filters_from_options([[tuple, input]])
75
52
 
76
53
  add_filter_steps(pipeline, input_filter)
77
54
  end
78
55
 
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", path_prefix: "output")
101
- end
102
-
103
56
  def pipe_for_composable_output(out_filters: [], initial_output_pipeline: initial_output_pipeline(add_default_ctx: Array(out_filters).empty?), **)
104
57
  out_filters = DSL::Tuple.filters_from_options(out_filters)
105
58
 
@@ -112,7 +65,6 @@ module Trailblazer
112
65
 
113
66
  Activity::TaskWrap::Pipeline.new(
114
67
  [
115
- Activity::TaskWrap::Pipeline.Row("output.init_hash", VariableMapping.method(:initial_aggregate)), # very first step
116
68
  default_ctx_row,
117
69
  Activity::TaskWrap::Pipeline.Row("output.merge_with_original", VariableMapping.method(:merge_with_original)), # last step
118
70
  ].compact
@@ -123,14 +75,6 @@ module Trailblazer
123
75
  ["output.default_output", VariableMapping.method(:default_output_ctx)]
124
76
  end
125
77
 
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, path_prefix: "inject")
132
- end
133
-
134
78
  def add_filter_steps(pipeline, rows, prepend_to: "input.scope", path_prefix: "input")
135
79
  rows = add_variables_steps_for_filters(rows, path_prefix: path_prefix)
136
80
 
@@ -145,48 +89,10 @@ module Trailblazer
145
89
  # @param filters [Array] List of {Filter} objects
146
90
  def add_variables_steps_for_filters(filters, path_prefix:)
147
91
  filters.collect do |filter|
148
- ["#{path_prefix}.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
92
+ ["#{path_prefix}.add_variables.#{filter.name}", filter] # FIXME: config name sucks, of course, if we want to allow inserting etc.
173
93
  end
174
94
  end
175
95
 
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
96
 
191
97
  # Keeps user's DSL configuration for a particular io-pipe step.
192
98
  # Implements the interface for the actual I/O code and is DSL code happening in the normalizer.
@@ -195,83 +101,272 @@ module Trailblazer
195
101
  # If a user needs to inject their own private iop step they can create this data structure with desired values here.
196
102
  # 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
103
 
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) }
104
+ class Tuple
105
+ def initialize(variable_name, add_variables_class, filters_builder, add_variables_class_for_callable=nil, insert_args: nil, **options)
106
+ @options =
107
+ {
108
+ variable_name: variable_name,
109
+ add_variables_class: add_variables_class,
110
+ filters_builder: filters_builder,
111
+ insert_args: insert_args,
112
+
113
+ add_variables_class_for_callable: add_variables_class_for_callable,
114
+
115
+ **options
116
+ }
201
117
  end
202
118
 
119
+ def to_h
120
+ @options
121
+ end
122
+
123
+ def self.filters_from_options(tuples_to_user_filters)
124
+ tuples_to_user_filters.collect { |tuple, user_filter| tuple.(user_filter) }.flatten(1)
125
+ end
203
126
 
204
127
  # @return [Filter] Filter instance that keeps {name} and {aggregate_step}.
205
128
  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)
129
+ @options[:filters_builder].(user_filter, **to_h)
210
130
  end
211
131
  end # TODO: implement {:insert_args}
212
132
 
213
133
  # 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
134
+ class In < Tuple
135
+ class FiltersBuilder
136
+ def self.call(user_filter, add_variables_class:, add_variables_class_for_callable:, type: :In, **options)
137
+ # In()/Out() => {:user => :current_user}
138
+ if user_filter.is_a?(Hash)
139
+ # For In(): build {SetVariable} filters.
140
+ # For Out(): build {SetVariable::Output} filters.
141
+ return Filter.build_filters_for_hash(user_filter, add_variables_class: add_variables_class) do |options, from_name, to_name|
142
+ options.merge(
143
+ name: Filter.name_for(type, "#{from_name.inspect}>#{to_name.inspect}"),
144
+ read_name: from_name,
145
+ write_name: to_name,
146
+ )
147
+ end
148
+ end
149
+
150
+ # In()/Out() => [:current_user]
151
+ if user_filter.is_a?(Array)
152
+ user_filter = Filter.hash_for(user_filter)
153
+
154
+ return Filter.build_filters_for_hash(user_filter, add_variables_class: add_variables_class) do |options, from_name, _|
155
+ options.merge(
156
+ name: Filter.name_for(type, from_name.inspect),
157
+ write_name: from_name,
158
+ read_name: from_name,
159
+ )
160
+ end
161
+ end
162
+
163
+ # callable, producing a hash!
164
+
165
+ return build_for_option(user_filter,
166
+ name: Filter.name_for(type, user_filter.object_id, :add_variables),
167
+ write_name: nil,
168
+ read_name: nil,
169
+ add_variables_class: add_variables_class_for_callable, # for example, {AddVariables::Output}
170
+ **options
171
+ )
172
+ # TODO: remove {add_variables_class_for_callable} and make everything SetVariable.
173
+ end # call
174
+
175
+ # Simply invoke user's filter.
176
+ # Use this for filters without condition and default.
177
+ def self.build_for_option(user_filter, **options)
178
+ filter = Activity::Circuit.Step(user_filter, option: true)
179
+
180
+ [
181
+ Filter.build(
182
+ filter: filter,
183
+ user_filter: user_filter,
184
+ **options
185
+ )
186
+ ]
187
+ end
188
+ end
189
+ end # In
190
+
191
+ class Out < Tuple
192
+ class FiltersBuilder
193
+ def self.call(user_filter, with_outer_ctx:, **options)
194
+ if with_outer_ctx
195
+ callable = user_filter # FIXME: :instance_method, for fuck's sake.
196
+ call_method = callable.respond_to?(:arity) ? callable : callable.method(:call)
197
+
198
+ options =
199
+ # TODO: remove {if} and only leave {else}.
200
+ if call_method.arity == 3
201
+ index = caller_locations.find_index { |location| location.to_s =~ /recompile_activity_for/ }
202
+ caller_location = caller_locations[index+2]
203
+
204
+ Activity::Deprecate.warn caller_location,
205
+ "The positional argument `outer_ctx` is deprecated, please use the `:outer_ctx` keyword argument.\n#{VariableMapping.deprecation_link}"
206
+
207
+ options.merge(
208
+ filter: Trailblazer::Option(user_filter),
209
+ add_variables_class_for_callable: AddVariables::Output::WithOuterContext_Deprecated, # old positional arg
210
+ )
211
+ else
212
+ options.merge(
213
+ add_variables_class_for_callable: AddVariables::Output::WithOuterContext,
214
+ )
215
+ end
216
+ end
217
+
218
+ In::FiltersBuilder.(user_filter, type: :Out, **options)
219
+ end
220
+ end
221
+ end # Out
216
222
 
217
- def self.In(name: rand, add_variables_class: AddVariables, filter_builder: method(:build_filter))
218
- In.new(name, add_variables_class, filter_builder)
223
+ def self.In(variable_name = nil, add_variables_class: SetVariable, filter_builder: In::FiltersBuilder, add_variables_class_for_callable: AddVariables)
224
+ In.new(variable_name, add_variables_class, filter_builder, add_variables_class_for_callable)
219
225
  end
220
226
 
221
227
  # 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
228
+ def self.Out(variable_name = nil, add_variables_class: SetVariable::Output, with_outer_ctx: false, delete: false, filter_builder: Out::FiltersBuilder, read_from_aggregate: false, add_variables_class_for_callable: AddVariables::Output)
229
+ add_variables_class = SetVariable::Output::Delete if delete
230
+ add_variables_class = SetVariable::ReadFromAggregate if read_from_aggregate
231
+
232
+ Out.new(
233
+ variable_name,
234
+ add_variables_class,
235
+ filter_builder,
236
+ add_variables_class_for_callable,
227
237
 
228
- Out.new(name, add_variables_class, filter_builder)
238
+ with_outer_ctx: with_outer_ctx,
239
+ )
229
240
  end
230
241
 
231
- def self.Inject()
232
- Inject.new
242
+ # Used in the DSL by you.
243
+ def self.Inject(variable_name = nil, override: false, **)
244
+ Inject.new(
245
+ variable_name,
246
+ nil, # add_variables_class # DISCUSS: do we really want that here?
247
+ Inject::FiltersBuilder,
248
+ nil,
249
+ override: override,
250
+ )
233
251
  end
234
252
 
235
253
  # 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
254
+ class Inject < Tuple
255
+ class FiltersBuilder
256
+ # Called via {Tuple#call}
257
+ def self.call(user_filter, add_variables_class:, variable_name:, **options)
258
+ # Build {SetVariable::Default}
259
+ if user_filter.is_a?(Hash) # TODO: deprecate in favor if {Inject(:variable_name)}!
260
+ return Filter.build_filters_for_hash(user_filter, add_variables_class: SetVariable::Default) do |options, from_name, user_proc|
261
+ options_with_condition_for_defaulted(
262
+ **options,
263
+ user_filter: user_proc,
264
+ write_name: from_name,
265
+ read_name: from_name,
266
+ )
267
+ end
268
+ end
269
+
270
+ # Build {SetVariable::Conditioned}
271
+ if user_filter.is_a?(Array)
272
+ user_filter = Filter.hash_for(user_filter)
273
+
274
+ return Filter.build_filters_for_hash(user_filter, add_variables_class: SetVariable::Conditioned) do |options, from_name, _|
275
+ options_with_condition(
276
+ **options,
277
+ write_name: from_name,
278
+ read_name: from_name,
279
+ user_filter: user_filter, # FIXME: this is not really helpful, it's something like [:field, :injects]
280
+ )
281
+ end
282
+ end
283
+
284
+ if options[:override]
285
+ return In::FiltersBuilder.build_for_option(
286
+ user_filter,
287
+ name: Filter.name_for(:Inject, variable_name, :add_variables),
288
+ write_name: variable_name,
289
+ read_name: nil,
290
+ add_variables_class: SetVariable,
291
+ **options
292
+ )
293
+ end
294
+
295
+
296
+ # Build {SetVariable::Default}
297
+ # {user_filter} is one of the following
298
+ # :instance_method
299
+ options = options_with_condition_for_defaulted(
300
+ **options,
301
+ write_name: variable_name,
302
+ read_name: variable_name,
303
+ user_filter: user_filter,
304
+ )
305
+
306
+ [
307
+ Filter.build_for_reading(add_variables_class: SetVariable::Default, **options)
308
+ ]
309
+ end # call
310
+
311
+ def self.options_with_condition(user_filter:, write_name:, name_specifier: nil, **options)
312
+ {
313
+ name: Filter.name_for(:Inject, write_name.inspect, name_specifier),
314
+ **options,
315
+ condition: VariablePresent.new(variable_name: write_name),
316
+ write_name: write_name,
317
+ user_filter: user_filter,
318
+ }
319
+ end
320
+
321
+ def self.options_with_condition_for_defaulted(user_filter:, **options)
322
+ default_filter = Activity::Circuit.Step(user_filter, option: true) # this is passed into {SetVariable.new}.
244
323
 
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)
324
+ options_with_condition(
325
+ **options,
326
+ user_filter: user_filter,
327
+ name_specifier: :default,
328
+ default_filter: default_filter,
329
+ )
330
+ end
331
+ end # FiltersBuilder
332
+ end # Inject
333
+
334
+ # DISCUSS: generic, again
335
+ module Filter
336
+ def self.build(add_variables_class:, **options)
337
+ add_variables_class.new(
338
+ **options,
339
+ )
251
340
  end
252
341
 
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.
342
+ def self.build_for_reading(read_name:, **options)
343
+ circuit_step_filter = VariableFromCtx.new(variable_name: read_name) # Activity::Circuit.Step(filter, option: true) # this is passed into {SetVariable.new}.
257
344
 
258
- filter_for(inject, inject_filter, name, "passthrough")
259
- end
345
+ build(
346
+ filter: circuit_step_filter,
347
+ **options
348
+ )
260
349
  end
261
350
 
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)} }
351
+ def self.build_filters_for_hash(user_filter, **options)
352
+ return user_filter.collect do |from_name, to_name|
353
+ options = yield(options, from_name, to_name)
266
354
 
267
- filter_for(inject, inject_filter, name, "defaulting_callable")
355
+ Filter.build_for_reading(
356
+ user_filter: user_filter,
357
+ **options,
358
+ )
268
359
  end
269
360
  end
270
361
 
271
- def self.filter_for(inject, inject_filter, name, type)
272
- DSL.In(name: "#{type}.#{name.inspect}", add_variables_class: AddVariables).(inject_filter)
362
+ def self.hash_for(ary)
363
+ ary.collect { |name| [name, name] }.to_h
273
364
  end
274
- end
365
+
366
+ def self.name_for(type, name, specifier=nil)
367
+ [type, specifier].compact.join(".") + "{#{name}}"
368
+ end
369
+ end # Filter
275
370
 
276
371
  end # DSL
277
372
  end