trailblazer 2.1.0.beta1 → 2.1.0.beta2

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.
data/CHANGES.md CHANGED
@@ -29,6 +29,10 @@ document Task API and define step API
29
29
  deprecate step->(options) ?
30
30
  injectable, per-operation step arguments strategy?
31
31
 
32
+ # 2.1.0.beta2
33
+
34
+ * Simplify `Nested` and several other internals by using the new `Activity` API.
35
+
32
36
  # 2.1.0.beta1
33
37
 
34
38
  * Add `deprecation/call` and `deprecation/context` that help with the new `call` API and symbols for `options` keys.
data/Gemfile CHANGED
@@ -14,12 +14,15 @@ gem "dry-auto_inject"
14
14
  gem "dry-matcher"
15
15
  gem "dry-validation"
16
16
 
17
- gem "trailblazer-operation", path: "../operation"
17
+ # gem "trailblazer-operation", path: "../operation"
18
18
  # gem "trailblazer-operation", github: "trailblazer/trailblazer-operation"
19
19
 
20
20
  gem "minitest-line"
21
21
 
22
- gem "trailblazer-activity", path: "../trailblazer-circuit"
22
+ gem "rubocop", require: false
23
+ # gem "trailblazer-activity", path: "../trailblazer-circuit"
24
+ # gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
25
+ # gem "trailblazer-activity", path: "../trailblazer-circuit"
23
26
  # gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
24
27
 
25
28
  # gem "trailblazer-context", path: "../trailblazer-context"
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
+ require "rubocop/rake_task"
3
4
 
4
5
  task :default => [:test]
5
6
 
@@ -14,3 +15,5 @@ Rake::TestTask.new(:testdep) do |test|
14
15
  test.test_files = FileList["test/deprecation/*_test.rb"]
15
16
  test.verbose = true
16
17
  end
18
+
19
+ RuboCop::RakeTask.new
data/lib/trailblazer.rb CHANGED
@@ -19,3 +19,4 @@ require "trailblazer/operation/nested"
19
19
  require "trailblazer/operation/wrap"
20
20
  require "trailblazer/operation/rescue"
21
21
  require "trailblazer/operation/inject"
22
+ require "trailblazer/operation/input_output"
@@ -6,72 +6,77 @@
6
6
  #
7
7
  # Needs Operation#model.
8
8
  # Needs #[], #[]= skill dependency.
9
- class Trailblazer::Operation
10
- module Contract
11
- def self.Build(name: "default", constant: nil, builder: nil)
12
- step = ->((options, flow_options), **circuit_options) { Build.(options, circuit_options, name: name, constant: constant, builder: builder) }
13
-
14
- task = Trailblazer::Activity::Task::Binary( step )
15
-
16
- { task: task, id: "contract.build" }
17
- end
18
-
19
- module Build
20
- # Build contract at runtime.
21
- def self.call(options, circuit_options, name: "default", constant: nil, builder: nil)
22
- # TODO: we could probably clean this up a bit at some point.
23
- contract_class = constant || options["contract.#{name}.class"] # DISCUSS: Injection possible here?
24
- model = options[:model]
25
- name = "contract.#{name}"
26
-
27
- options[name] =
28
- if builder
29
- call_builder( options, circuit_options, builder: builder, constant: contract_class, name: name )
30
- else
31
- contract_class.new(model)
32
- end
9
+ module Trailblazer
10
+ class Operation
11
+ module Contract
12
+ def self.Build(name: "default", constant: nil, builder: nil)
13
+ task = ->((options, flow_options), **circuit_options) do
14
+ result = Build.(options, circuit_options, name: name, constant: constant, builder: builder)
15
+
16
+ return Activity::TaskBuilder::Binary.binary_direction_for( result, Activity::Right, Activity::Left ),
17
+ [options, flow_options]
18
+ end
19
+
20
+ { task: task, id: "contract.build" }
33
21
  end
34
22
 
35
- def self.call_builder(options, circuit_options, builder:raise, constant:raise, name:raise)
36
- # builder_options = Trailblazer::Context( options, constant: constant, name: name ) # options.merge( .. )
37
-
38
- # Trailblazer::Option::KW(builder).(builder_options, circuit_options)
23
+ module Build
24
+ # Build contract at runtime.
25
+ def self.call(options, circuit_options, name: "default", constant: nil, builder: nil)
26
+ # TODO: we could probably clean this up a bit at some point.
27
+ contract_class = constant || options["contract.#{name}.class"] # DISCUSS: Injection possible here?
28
+ model = options[:model]
29
+ name = "contract.#{name}"
39
30
 
31
+ options[name] =
32
+ if builder
33
+ call_builder( options, circuit_options, builder: builder, constant: contract_class, name: name )
34
+ else
35
+ contract_class.new(model)
36
+ end
37
+ end
40
38
 
39
+ def self.call_builder(options, circuit_options, builder:raise, constant:raise, name:raise)
40
+ # builder_options = Trailblazer::Context( options, constant: constant, name: name ) # options.merge( .. )
41
41
 
42
+ # Trailblazer::Option::KW(builder).(builder_options, circuit_options)
42
43
 
43
44
 
44
45
 
45
46
 
46
47
 
47
- # FIXME: almost identical with Option::KW.
48
- # FIXME: see Nested::Options::Dynamic, the same shit
49
- tmp_options = options.to_hash.merge(
50
- constant: constant,
51
- name: name
52
- )
53
48
 
54
- Trailblazer::Option(builder).( options, tmp_options, circuit_options )
55
- end
56
- end
57
49
 
58
- module DSL
59
- def self.extended(extender)
60
- extender.extend(ClassDependencies)
61
- warn "[Trailblazer] Using `contract do...end` is deprecated. Please use a form class and the Builder( constant: <Form> ) option."
62
- end
63
- # This is the class level DSL method.
64
- # Op.contract #=> returns contract class
65
- # Op.contract do .. end # defines contract
66
- # Op.contract CommentForm # copies (and subclasses) external contract.
67
- # Op.contract CommentForm do .. end # copies and extends contract.
68
- def contract(name=:default, constant=nil, base: Reform::Form, &block)
69
- heritage.record(:contract, name, constant, &block)
70
50
 
71
- path, form_class = Trailblazer::DSL::Build.new.({ prefix: :contract, class: base, container: self }, name, constant, block)
51
+ # FIXME: almost identical with Option::KW.
52
+ # FIXME: see Nested::Options::Dynamic, the same shit
53
+ tmp_options = options.to_hash.merge(
54
+ constant: constant,
55
+ name: name
56
+ )
72
57
 
73
- self[path] = form_class
58
+ Trailblazer::Option(builder).( options, tmp_options, circuit_options )
59
+ end
74
60
  end
75
- end # Contract
61
+
62
+ module DSL
63
+ def self.extended(extender)
64
+ extender.extend(ClassDependencies)
65
+ warn "[Trailblazer] Using `contract do...end` is deprecated. Please use a form class and the Builder( constant: <Form> ) option."
66
+ end
67
+ # This is the class level DSL method.
68
+ # Op.contract #=> returns contract class
69
+ # Op.contract do .. end # defines contract
70
+ # Op.contract CommentForm # copies (and subclasses) external contract.
71
+ # Op.contract CommentForm do .. end # copies and extends contract.
72
+ def contract(name=:default, constant=nil, base: Reform::Form, &block)
73
+ heritage.record(:contract, name, constant, &block)
74
+
75
+ path, form_class = Trailblazer::DSL::Build.new.({ prefix: :contract, class: base, container: self }, name, constant, block)
76
+
77
+ self[path] = form_class
78
+ end
79
+ end # Contract
80
+ end
76
81
  end
77
82
  end
@@ -0,0 +1,28 @@
1
+ module Trailblazer
2
+ # Add an input and output filter for a task, allowing to control what a task "sees"
3
+ # (receives as input) and returns (or, what the outer caller "sees").
4
+ #
5
+ # This works by adding two variable mappers to the taskWrap.
6
+ # One before the actual task gets called (input) and one before the end (output).
7
+ module Operation::InputOutput
8
+ # naming: Macaroni, VariableMapping
9
+ def self.plan(input, output)
10
+ default_input_filter = ->(options, *) { ctx = options }
11
+ default_output_filter = ->(options, *) { options }
12
+
13
+ input ||= default_input_filter
14
+ output ||= default_output_filter
15
+
16
+ input_filter = Activity::TaskWrap::Input.new(input)
17
+ output_filter = Activity::TaskWrap::Output.new(output)
18
+
19
+ # taskWrap extensions
20
+ Module.new do
21
+ extend Activity::Path::Plan()
22
+
23
+ task input_filter, id: ".input", before: "task_wrap.call_task"
24
+ task output_filter, id: ".output", before: "End.success", group: :end # DISCUSS: position
25
+ end
26
+ end
27
+ end
28
+ end
@@ -2,16 +2,16 @@ class Trailblazer::Operation
2
2
  def self.Model(model_class, action=nil)
3
3
  # step = Pipetree::Step.new(step, "model.class" => model_class, "model.action" => action)
4
4
 
5
- task = Railway::TaskBuilder.( Model.new )
5
+ task = Trailblazer::Activity::TaskBuilder::Binary.( Model.new )
6
6
 
7
- runner_options = {
8
- merge: Wrap::Inject::Defaults(
7
+ extension = Trailblazer::Activity::TaskWrap::Merge.new(
8
+ Wrap::Inject::Defaults(
9
9
  "model.class" => model_class,
10
10
  "model.action" => action
11
11
  )
12
- }
12
+ )
13
13
 
14
- { task: task, id: "model.build", runner_options: runner_options }
14
+ { task: task, id: "model.build", extension: [extension] }
15
15
  end
16
16
 
17
17
  class Model
@@ -2,43 +2,40 @@
2
2
  module Trailblazer
3
3
  class Operation
4
4
  def self.Nested(callable, input:nil, output:nil, id: "Nested(#{callable})")
5
- task_wrap_wirings = []
6
- task, operation = Nested.build(callable, input, output)
7
-
8
- # @needs operation#outputs
9
-
10
- # TODO: move this to the generic step DSL
11
- task_wrap_extensions = []
12
-
13
- if input || output
14
- default_input_filter = ->(options, *) { ctx = options }
15
- default_output_filter = ->(options, *) { options }
5
+ task_wrap_extensions = Module.new do
6
+ extend Activity::Path::Plan()
7
+ end
16
8
 
17
- input ||= default_input_filter
18
- output ||= default_output_filter
9
+ task, operation, is_dynamic = Nested.build(callable)
19
10
 
20
- input_filter = Activity::Wrap::Input.new(input)
21
- output_filter = Activity::Wrap::Output.new(output)
11
+ if input || output # TODO: move this to the generic step DSL
12
+ task_wrap_extensions = InputOutput.plan( input, output )
13
+ end
22
14
 
23
- task_wrap_extensions = Activity::Magnetic::Builder::Path.plan do
24
- task input_filter, id: ".input", before: "task_wrap.call_task"
25
- task output_filter, id: ".output", before: "End.success", group: :end # DISCUSS: position
26
- end
15
+ if is_dynamic
16
+ # task_wrap_extensions += Activity::Magnetic::Builder::Path.plan do
17
+ task_wrap_extensions.task task.method(:compute_nested_activity), id: ".compute_nested_activity", after: "Start.default", group: :start
18
+ task_wrap_extensions.task task.method(:compute_return_signal), id: ".compute_return_signal", after: "task_wrap.call_task"
19
+ # end
27
20
  end
28
- # Default {Output} copies the mutable data from the nested activity into the original.
29
21
 
30
- { task: task, id: id, runner_options: { merge: task_wrap_extensions }, plus_poles: Activity::Magnetic::DSL::PlusPoles.from_outputs(operation.outputs) }
22
+ {
23
+ task: task,
24
+ id: id,
25
+ extension: [ Trailblazer::Activity::TaskWrap::Merge.new(task_wrap_extensions) ],
26
+ plus_poles: Activity::Magnetic::DSL::PlusPoles.from_outputs(operation.outputs) # @needs operation#outputs
27
+ }
31
28
  end
32
29
 
33
30
  # @private
34
31
  module Nested
35
- def self.build(nested_operation, input, output) # DISCUSS: use builders here?
36
- return dynamic = Dynamic.new(nested_operation), dynamic unless nestable_object?(nested_operation)
32
+ def self.build(nested_operation) # DISCUSS: use builders here?
33
+ return dynamic = Dynamic.new(nested_operation), dynamic, true unless nestable_object?(nested_operation)
37
34
 
38
35
  # The returned {Nested} instance is a valid circuit element and will be `call`ed in the circuit.
39
36
  # It simply returns the nested activity's `signal,options,flow_options` return set.
40
37
  # The actual wiring - where to go with that - is done by the step DSL.
41
- return Trailblazer::Activity::Subprocess(nested_operation, call: :__call__), nested_operation
38
+ return Trailblazer::Activity::Subprocess(nested_operation, call: :__call__), nested_operation, false
42
39
  end
43
40
 
44
41
  def self.nestable_object?(object)
@@ -56,9 +53,9 @@ module Trailblazer
56
53
  #
57
54
  # This is what {Nested} in 2.0 used to do, where the outcome could only be true/false (or success/failure).
58
55
  class Dynamic
59
- def initialize(wrapped)
60
- @wrapped = Trailblazer::Option::KW(wrapped)
61
- @outputs = {
56
+ def initialize(nested_activity)
57
+ @nested_activity = Trailblazer::Option::KW(nested_activity)
58
+ @outputs = {
62
59
  :success => Activity::Output( Railway::End::Success.new(:success), :success ),
63
60
  :failure => Activity::Output( Railway::End::Failure.new(:failure), :failure ),
64
61
  }
@@ -66,17 +63,26 @@ module Trailblazer
66
63
 
67
64
  attr_reader :outputs
68
65
 
69
- def call( (options, flow_options), **circuit_options )
70
- activity = @wrapped.(options, circuit_options) # evaluate the option to get the actual "object" to call.
66
+ # TaskWrap step.
67
+ def compute_nested_activity( (wrap_ctx, original_args), **circuit_options )
68
+ (ctx, _), original_circuit_options = original_args
69
+
70
+ activity = @nested_activity.( ctx, original_circuit_options ) # evaluate the option to get the actual "object" to call.
71
71
 
72
- signal, args = activity.__call__( [options, flow_options], **circuit_options )
72
+ # overwrite :task so task_wrap.call_task will call this activity. This is a trick so we don't have to repeat
73
+ # logic from #call_task here.
74
+ wrap_ctx[:task] = Trailblazer::Activity::Subprocess( activity, call: :__call__ )
73
75
 
76
+ return Activity::Right, [ wrap_ctx, original_args ]
77
+ end
78
+
79
+ def compute_return_signal( (wrap_ctx, original_args), **circuit_options )
74
80
  # Translate the genuine nested signal to the generic Dynamic end (success/failure, only).
75
81
  # Note that here we lose information about what specific event was emitted.
76
- [
77
- signal.kind_of?(Railway::End::Success) ? @outputs[:success].signal : @outputs[:failure].signal,
78
- args
79
- ]
82
+ wrap_ctx[:return_signal] = wrap_ctx[:return_signal].kind_of?(Railway::End::Success) ?
83
+ @outputs[:success].signal : @outputs[:failure].signal
84
+
85
+ return Activity::Right, [ wrap_ctx, original_args ]
80
86
  end
81
87
  end
82
88
  end
@@ -1,12 +1,14 @@
1
- class Trailblazer::Operation
2
- module Contract
3
- def self.Persist(method: :save, name: "default")
4
- path = "contract.#{name}"
5
- step = ->(options, **) { options[path].send(method) }
1
+ module Trailblazer
2
+ class Operation
3
+ module Contract
4
+ def self.Persist(method: :save, name: "default")
5
+ path = "contract.#{name}"
6
+ step = ->(options, **) { options[path].send(method) }
6
7
 
7
- task = Railway::TaskBuilder.( step )
8
+ task = Activity::TaskBuilder::Binary.( step )
8
9
 
9
- { task: task, id: "persist.save" }
10
+ { task: task, id: "persist.save" }
11
+ end
10
12
  end
11
13
  end
12
14
  end
@@ -32,13 +32,13 @@ class Trailblazer::Operation
32
32
 
33
33
  task = Eval.new( name: name, path: path )
34
34
 
35
- runner_options = {
36
- merge: Trailblazer::Operation::Wrap::Inject::Defaults(
35
+ extension = Trailblazer::Activity::TaskWrap::Merge.new(
36
+ Trailblazer::Operation::Wrap::Inject::Defaults(
37
37
  path => condition
38
38
  )
39
- }
39
+ )
40
40
 
41
- { task: task, id: path, runner_options: runner_options }
41
+ { task: task, id: path, extension: [extension] }
42
42
  end
43
43
  end
44
44
  end
@@ -1,75 +1,74 @@
1
- class Trailblazer::Operation
2
- module Contract
3
- # result.contract = {..}
4
- # result.contract.errors = {..}
5
- # Deviate to left track if optional key is not found in params.
6
- # Deviate to left if validation result falsey.
7
- def self.Validate(skip_extract: false, name: "default", representer: false, key: nil) # DISCUSS: should we introduce something like Validate::Deserializer?
8
- params_path = "contract.#{name}.params" # extract_params! save extracted params here.
1
+ module Trailblazer
2
+ class Operation
3
+ module Contract
4
+ # result.contract = {..}
5
+ # result.contract.errors = {..}
6
+ # Deviate to left track if optional key is not found in params.
7
+ # Deviate to left if validation result falsey.
8
+ def self.Validate(skip_extract: false, name: "default", representer: false, key: nil) # DISCUSS: should we introduce something like Validate::Deserializer?
9
+ params_path = "contract.#{name}.params" # extract_params! save extracted params here.
9
10
 
10
- extract = Validate::Extract.new( key: key, params_path: params_path ).freeze
11
- validate = Validate.new( name: name, representer: representer, params_path: params_path ).freeze
11
+ extract = Validate::Extract.new( key: key, params_path: params_path ).freeze
12
+ validate = Validate.new( name: name, representer: representer, params_path: params_path ).freeze
12
13
 
13
- # Return the Validate::Call task if the first step, the params extraction, is not desired.
14
- if skip_extract || representer
15
- return { task: Trailblazer::Activity::Task::Binary( validate ), id: "contract.#{name}.call" }
16
- end
14
+ # Build a simple Railway {Activity} for the internal flow.
15
+ activity = Module.new do
16
+ extend Activity::Railway()
17
+
18
+ step extract, id: "#{params_path}_extract" unless skip_extract || representer
19
+ step validate, id: "contract.#{name}.call"
20
+ end
17
21
 
22
+ # activity, _ = activity.decompose
18
23
 
19
- # Build a simple Railway {Activity} for the internal flow.
20
- activity = Trailblazer::Activity::Railway.build do # FIXME: make Activity.build(builder: Railway) do end an <Activity>
21
- step Trailblazer::Activity::Task::Binary( extract ), id: "#{params_path}_extract"
22
- step Trailblazer::Activity::Task::Binary( validate ), id: "contract.#{name}.call"
24
+ # DISCUSS: use Nested here?
25
+ { task: activity, id: "contract.#{name}.validate", plus_poles: Activity::Magnetic::DSL::PlusPoles.from_outputs(activity.outputs) }
23
26
  end
24
27
 
25
- # DISCUSS: use Nested here?
26
- # Nested.operation_class.Nested( activity, id: "contract.#{name}.validate" )
27
- { task: activity, id: "contract.#{name}.validate", plus_poles: Trailblazer::Activity::Magnetic::DSL::PlusPoles.from_outputs(activity.outputs) }
28
- end
28
+ class Validate
29
+ # Task: extract the contract's input from params by reading `:key`.
30
+ class Extract
31
+ def initialize(key:nil, params_path:nil)
32
+ @key, @params_path = key, params_path
33
+ end
29
34
 
30
- class Validate
31
- # Task: extract the contract's input from params by reading `:key`.
32
- class Extract
33
- def initialize(key:nil, params_path:nil)
34
- @key, @params_path = key, params_path
35
+ def call( ctx, params:, ** )
36
+ ctx[@params_path] = @key ? params[@key] : params
37
+ end
35
38
  end
36
39
 
37
- def call( (options, flow_options), **circuit_options )
38
- options[@params_path] = @key ? options[:params][@key] : options[:params]
40
+ def initialize(name:"default", representer:false, params_path:nil)
41
+ @name, @representer, @params_path = name, representer, params_path
39
42
  end
40
- end
41
43
 
42
- def initialize(name:"default", representer:false, params_path:nil)
43
- @name, @representer, @params_path = name, representer, params_path
44
- end
45
-
46
- # Task: Validates contract `:name`.
47
- def call( (options, flow_options), **circuit_options )
48
- validate!(
49
- options,
50
- representer: options["representer.#{@name}.class"] ||= @representer, # FIXME: maybe @representer should use DI.
51
- params_path: @params_path
52
- )
53
- end
44
+ # Task: Validates contract `:name`.
45
+ def call( ctx, ** )
46
+ validate!(
47
+ ctx,
48
+ representer: ctx["representer.#{@name}.class"] ||= @representer, # FIXME: maybe @representer should use DI.
49
+ params_path: @params_path
50
+ )
51
+ end
54
52
 
55
- def validate!(options, representer:false, from: :document, params_path:nil)
56
- path = "contract.#{@name}"
57
- contract = options[path]
53
+ def validate!(options, representer:false, from: :document, params_path:nil)
54
+ path = "contract.#{@name}"
55
+ contract = options[path]
58
56
 
59
- # this is for 1.1-style compatibility and should be removed once we have Deserializer in place:
60
- options["result.#{path}"] = result =
61
- if representer
62
- # use :document as the body and let the representer deserialize to the contract.
63
- # this will be simplified once we have Deserializer.
64
- # translates to contract.("{document: bla}") { MyRepresenter.new(contract).from_json .. }
65
- contract.(options[from]) { |document| representer.new(contract).parse(document) }
66
- else
67
- # let Reform handle the deserialization.
68
- contract.(options[params_path])
69
- end
57
+ # this is for 1.1-style compatibility and should be removed once we have Deserializer in place:
58
+ options["result.#{path}"] = result =
59
+ if representer
60
+ # use :document as the body and let the representer deserialize to the contract.
61
+ # this will be simplified once we have Deserializer.
62
+ # translates to contract.("{document: bla}") { MyRepresenter.new(contract).from_json .. }
63
+ contract.(options[from]) { |document| representer.new(contract).parse(document) }
64
+ else
65
+ # let Reform handle the deserialization.
66
+ contract.(options[params_path])
67
+ end
70
68
 
71
- result.success?
69
+ result.success?
70
+ end
72
71
  end
73
72
  end
74
- end
73
+ end # Operation
75
74
  end