trailblazer 2.1.0.beta1 → 2.1.0.beta2

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