trailblazer 2.0.7 → 2.1.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +35 -1
  3. data/Gemfile +6 -12
  4. data/README.md +3 -1
  5. data/Rakefile +6 -17
  6. data/lib/trailblazer.rb +7 -4
  7. data/lib/trailblazer/deprecation/call.rb +46 -0
  8. data/lib/trailblazer/deprecation/context.rb +43 -0
  9. data/lib/trailblazer/operation/contract.rb +40 -9
  10. data/lib/trailblazer/operation/deprecations.rb +21 -0
  11. data/lib/trailblazer/operation/guard.rb +5 -5
  12. data/lib/trailblazer/operation/model.rb +15 -10
  13. data/lib/trailblazer/operation/nested.rb +56 -85
  14. data/lib/trailblazer/operation/persist.rb +4 -2
  15. data/lib/trailblazer/operation/policy.rb +16 -7
  16. data/lib/trailblazer/operation/pundit.rb +3 -3
  17. data/lib/trailblazer/operation/representer.rb +5 -0
  18. data/lib/trailblazer/operation/rescue.rb +12 -9
  19. data/lib/trailblazer/operation/validate.rb +36 -29
  20. data/lib/trailblazer/operation/wrap.rb +49 -11
  21. data/lib/trailblazer/task.rb +20 -0
  22. data/lib/trailblazer/version.rb +1 -1
  23. data/test/benchmark.rb +63 -0
  24. data/test/deprecation/call_test.rb +42 -0
  25. data/test/deprecation/context_test.rb +19 -0
  26. data/test/docs/contract_test.rb +73 -53
  27. data/test/docs/dry_test.rb +2 -2
  28. data/test/docs/fast_test.rb +133 -13
  29. data/test/docs/guard_test.rb +28 -35
  30. data/test/docs/macro_test.rb +1 -1
  31. data/test/docs/model_test.rb +13 -13
  32. data/test/docs/nested_test.rb +54 -122
  33. data/test/docs/operation_test.rb +42 -43
  34. data/test/docs/pundit_test.rb +16 -16
  35. data/test/docs/representer_test.rb +18 -18
  36. data/test/docs/rescue_test.rb +29 -29
  37. data/test/docs/trace_test.rb +82 -0
  38. data/test/docs/wrap_test.rb +59 -26
  39. data/test/module_test.rb +75 -75
  40. data/test/nested_test.rb +293 -0
  41. data/test/operation/contract_test.rb +23 -153
  42. data/test/operation/dsl/contract_test.rb +9 -9
  43. data/test/operation/dsl/representer_test.rb +169 -169
  44. data/test/operation/model_test.rb +15 -21
  45. data/test/operation/persist_test.rb +18 -11
  46. data/test/operation/pundit_test.rb +25 -23
  47. data/test/operation/representer_test.rb +254 -254
  48. data/test/test_helper.rb +5 -2
  49. data/test/variables_test.rb +158 -0
  50. data/trailblazer.gemspec +1 -1
  51. data/untitled +33 -0
  52. metadata +25 -27
  53. data/lib/trailblazer/operation/callback.rb +0 -35
  54. data/lib/trailblazer/operation/procedural/contract.rb +0 -15
  55. data/lib/trailblazer/operation/procedural/validate.rb +0 -22
  56. data/test/operation/callback_test.rb +0 -70
  57. data/test/operation/dsl/callback_test.rb +0 -106
  58. data/test/operation/params_test.rb +0 -36
  59. data/test/operation/pipedream_test.rb +0 -59
  60. data/test/operation/pipetree_test.rb +0 -104
  61. data/test/operation/present_test.rb +0 -24
  62. data/test/operation/resolver_test.rb +0 -47
  63. data/test/operation_test.rb +0 -143
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bf6240c4f189a477366736fe061c493f08c6949b
4
- data.tar.gz: 68b716f9d99c124fc6d86f1e471c21478d25c0ae
3
+ metadata.gz: c82134611daa3efe9309eebb4a283585b4095428
4
+ data.tar.gz: cc121cdb03c9ea4da12b9b2713150e50d18ebac9
5
5
  SHA512:
6
- metadata.gz: 3ea12ffe486f4f9c50ac9a839cd3b6429a36c7e6be2c6dd889244f025d7dbe4b21b50b45f56378b77fdddba1c819cdaf81b204d32aed90ce606b846ba561aac8
7
- data.tar.gz: 0cbc2fc6e6f3e3e38019cf3181373803fc0828a12799bd6239705f77786eafacc9d339e1c48f3939876aa93473a5a49ce1a5f50b2651d50f5a5489f9b59d0b8b
6
+ metadata.gz: 992b2af33d7b5de098b59c2eb9a75b6dd4854d0a580613b6938c6ae44f5ec2d048f4686cfecb23c4daf5673c78b24aa90286b50e5df24bc856134155f7e9d7f4
7
+ data.tar.gz: 96912060658256a931dec0a8951c61fb3bc1eb62521a4730a2458258f2e1e7c145adad129820c19149d578213c45c81673587f1f1afe16bec9c1a189ad32c914
data/CHANGES.md CHANGED
@@ -1,3 +1,38 @@
1
+ # 2.1
2
+
3
+ * Macros now always have to provide an `:id`. This was a bit fuzzy in 2.0.
4
+
5
+ * Nested
6
+ if Nested( Edit ), outputs will automatically be connected, see editor.
7
+ * Wrap
8
+ dropped the `pipe` option. This is now `options, flow_options, *`
9
+ `false` is now automatically connected to End.failure.
10
+
11
+ * `operation.new` step removed.
12
+ * Undocumented step behavior removed. You can't write to `self` anymore.
13
+
14
+ ```ruby
15
+ step :process
16
+ def process(*)
17
+ self["x"] = true
18
+ end
19
+ ```
20
+
21
+ Always write to `options`.
22
+
23
+ * self[] removed
24
+ * Fixed `Guard` where procs could receive one argument, only. Guards follow the step interface: `Policy::Guard( ->(options, **) { .. } )
25
+ * Removed `Operation::Callback` which was a poor idea and luckily no one was using it.
26
+
27
+ TODO:
28
+ document Task API and define step API
29
+ deprecate step->(options) ?
30
+ injectable, per-operation step arguments strategy?
31
+
32
+ # 2.1.0.beta1
33
+
34
+ * Add `deprecation/call` and `deprecation/context` that help with the new `call` API and symbols for `options` keys.
35
+
1
36
  # 2.0.7
2
37
 
3
38
  * Allow to use any method with the Model macro, e.g.
@@ -8,7 +43,6 @@
8
43
 
9
44
  will now invoke `Comment[ params[:id] ]`, which makes using Sequel a breeze.
10
45
 
11
-
12
46
  # 2.0.6
13
47
 
14
48
  * Fix what we broke in 2.0.5, where `Wrap` would always use the current operation subclass and not the empty `Trailblazer::Operation`. Thanks to @mensfeld.
data/Gemfile CHANGED
@@ -3,29 +3,23 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in trailblazer.gemspec
4
4
  gemspec
5
5
 
6
- # gem "representable", path: "../representable"
7
- # gem "disposable", path: "../disposable"
8
- # gem "reform", github: "apotonick/reform"
9
6
  gem "reform-rails"
10
- gem "activesupport", "~> 4.2.0"
7
+ gem "activesupport"#, "~> 4.2.0"
11
8
 
12
- gem "roar", github: "apotonick/roar"
13
9
  # gem "reform", "~> 2.0.0"
14
10
  gem "reform"#, path: "../reform"
15
- # gem "roar", path: "../roar"
16
11
  gem "multi_json"
17
12
 
18
13
  gem "dry-auto_inject"
19
14
  gem "dry-matcher"
20
15
  gem "dry-validation"
21
16
 
22
-
23
- # gem "trailblazer-operation", path: "../operation"
24
- # gem "pipetree", path: "../pipetree"
17
+ gem "trailblazer-operation", path: "../operation"
25
18
  # gem "trailblazer-operation", github: "trailblazer/trailblazer-operation"
26
- # gem "pipetree", github: "apotonick/pipetree"
27
-
28
19
 
29
20
  gem "minitest-line"
30
21
 
31
- # gem "uber", path: "../uber"
22
+ gem "trailblazer-activity", path: "../trailblazer-circuit"
23
+ # gem "trailblazer-activity", github: "trailblazer/trailblazer-activity"
24
+
25
+ # gem "trailblazer-context", path: "../trailblazer-context"
data/README.md CHANGED
@@ -6,7 +6,9 @@ _Trailblazer provides new high-level abstractions for Ruby frameworks. It gently
6
6
  [![TRB Newsletter](https://img.shields.io/badge/TRB-newsletter-lightgrey.svg)](http://trailblazer.to/newsletter/)
7
7
  [![Gem Version](https://badge.fury.io/rb/trailblazer.svg)](http://badge.fury.io/rb/trailblazer)
8
8
 
9
- **This document discusses Trailblazer 2.0. The [1.x documentation is here](http://trailblazer.to/gems/operation/1.1/).**
9
+ **This document discusses Trailblazer 2.1.** An overview about the additions are [on our website](http://trailblazer.to/blog/2017-12-trailblazer-2-1-what-you-need-to-know.html).
10
+
11
+ The [1.x documentation is here](http://trailblazer.to/gems/operation/1.1/).
10
12
 
11
13
  ## Trailblazer In A Nutshell
12
14
 
data/Rakefile CHANGED
@@ -5,23 +5,12 @@ task :default => [:test]
5
5
 
6
6
  Rake::TestTask.new(:test) do |test|
7
7
  test.libs << 'test'
8
- # test.test_files = FileList['test/**/*_test.rb']
9
- test_files = FileList[%w{
10
- test/operation/pundit_test.rb
11
- test/operation/model_test.rb
12
- test/operation/contract_test.rb
13
- test/operation/persist_test.rb
14
- test/operation/callback_test.rb
15
- test/operation/resolver_test.rb
16
- test/operation/dsl/contract_test.rb
17
-
18
- test/docs/*_test.rb
19
- }]
20
-
21
- if RUBY_VERSION == "1.9.3"
22
- test_files = test_files - %w{test/docs/dry_test.rb test/docs/auto_inject_test.rb}
23
- end
8
+ test.test_files = FileList['test/**/*_test.rb'] - FileList["test/deprecation/*_test.rb"]
9
+ test.verbose = true
10
+ end
24
11
 
25
- test.test_files = test_files #- ["test/docs/rescue_test.rb"]
12
+ Rake::TestTask.new(:testdep) do |test|
13
+ test.libs << 'test'
14
+ test.test_files = FileList["test/deprecation/*_test.rb"]
26
15
  test.verbose = true
27
16
  end
@@ -1,8 +1,11 @@
1
- require "trailblazer/operation"
2
- require "trailblazer/operation/pipetree"
1
+ require "trailblazer/version"
3
2
 
3
+ require "trailblazer/operation"
4
4
  require "trailblazer/dsl"
5
- require "trailblazer/version"
5
+
6
+ require "trailblazer/task"
7
+
8
+ require "trailblazer/operation/deprecations"
6
9
 
7
10
  require "trailblazer/operation/model"
8
11
  require "trailblazer/operation/contract"
@@ -12,7 +15,7 @@ require "trailblazer/operation/policy"
12
15
  require "trailblazer/operation/pundit"
13
16
  require "trailblazer/operation/guard"
14
17
  require "trailblazer/operation/persist"
15
- # require "trailblazer/operation/callback"
16
18
  require "trailblazer/operation/nested"
17
19
  require "trailblazer/operation/wrap"
18
20
  require "trailblazer/operation/rescue"
21
+ require "trailblazer/operation/inject"
@@ -0,0 +1,46 @@
1
+ module Trailblazer
2
+ module Deprecation
3
+ module Operation
4
+ # This is super hacky. Fix you call calls everywhere (shouldn't take too long) and never load this file, again.
5
+ module Call
6
+ def self.options_for_call(params, *containers)
7
+ if containers == []
8
+ if params.is_a?(Hash) # this means we assume everything is cool. Create.( {...} )
9
+
10
+ else # this means someone did Create.( #<WeirdParamsObject> )
11
+ deprecate_positional_params(params, *containers)
12
+ return { params: params }, *containers
13
+ end
14
+ else # Create.( params, "current_user" => ... )
15
+ options, containers = containers[0], (containers[1..-1] || [])
16
+ if options.is_a?(Hash) # old API
17
+ warn "[Trailblazer] Please don't pass the `params` object as a positional argument into `Operation.()`, use the `:params` key and one hash for all: `Operation.( params: my_params, current_user: ... )` ."
18
+ return options.merge( params: params ), *containers
19
+ end
20
+ end
21
+
22
+ return params, *containers
23
+ end
24
+
25
+ def self.deprecate_positional_params(params, *containers)
26
+ warn "[Trailblazer] Please don't pass the `params` object as a positional argument into `Operation.()`, use the `:params` key: `Operation.( params: my_params )` ."
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ Trailblazer::Operation.module_eval do
34
+ # this sucks:
35
+ def self.call(options={}, *containers)
36
+ options, *containers = Trailblazer::Deprecation::Operation::Call.options_for_call(options, *containers)
37
+
38
+ ctx = Trailblazer::Operation::PublicCall.options_for_public_call(options, *containers)
39
+
40
+ # call the activity.
41
+ last_signal, (options, flow_options) = __call__( [ctx, {}] ) # Railway::call # DISCUSS: this could be ::call_with_context.
42
+
43
+ # Result is successful if the activity ended with an End event derived from Railway::End::Success.
44
+ Trailblazer::Operation::Railway::Result(last_signal, options, flow_options)
45
+ end
46
+ end
@@ -0,0 +1,43 @@
1
+ module Trailblazer
2
+ module Deprecation
3
+ class ContextWithIndifferentAccess < Trailblazer::Context
4
+ def [](key)
5
+ return super unless Trailblazer::Operation::PublicCall.deprecatable?(key)
6
+ key, _ = Trailblazer::Operation::PublicCall.deprecate_string(key, nil)
7
+ super(key)
8
+ end
9
+
10
+ def []=(key, value)
11
+ return super unless Trailblazer::Operation::PublicCall.deprecatable?(key)
12
+ key, _ = Trailblazer::Operation::PublicCall.deprecate_string(key, nil)
13
+ super(key, value)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ Trailblazer::Operation::PublicCall.module_eval do
20
+ def self.options_for_public_call(options={}, *containers)
21
+ hash_transformer = ->(containers) { containers[0].to_hash } # FIXME: don't transform any containers into kw args.
22
+
23
+ options = deprecate_strings(options)
24
+
25
+ immutable_options = Trailblazer::Context::ContainerChain.new( [options, *containers], to_hash: hash_transformer ) # Runtime options, immutable.
26
+
27
+ Trailblazer::Deprecation::ContextWithIndifferentAccess.new(immutable_options, {})
28
+ end
29
+
30
+ def self.deprecatable?(key)
31
+ key.is_a?(String) && key.split(".").size == 1
32
+ end
33
+
34
+ def self.deprecate_strings(options)
35
+ ary = options.collect { |k,v| deprecatable?(k) ? deprecate_string(k, v) : [k,v] }
36
+ Hash[ary]
37
+ end
38
+
39
+ def self.deprecate_string(key, value)
40
+ warn "[Trailblazer] Using a string key for non-namespaced keys is deprecated. Please use `:#{key}` instead of `#{key.inspect}`."
41
+ [ key.to_sym, value ]
42
+ end
43
+ end
@@ -8,27 +8,58 @@
8
8
  # Needs #[], #[]= skill dependency.
9
9
  class Trailblazer::Operation
10
10
  module Contract
11
- def self.Build(name:"default", constant:nil, builder: nil)
12
- step = ->(input, options) { Build.for(input, options, name: name, constant: constant, builder: builder) }
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
13
 
14
- [ step, name: "contract.build" ]
14
+ task = Trailblazer::Activity::Task::Binary( step )
15
+
16
+ { task: task, id: "contract.build" }
15
17
  end
16
18
 
17
19
  module Build
18
- # bla build contract at runtime.
19
- def self.for(operation, options, name:"default", constant:nil, builder: nil)
20
+ # Build contract at runtime.
21
+ def self.call(options, circuit_options, name: "default", constant: nil, builder: nil)
20
22
  # TODO: we could probably clean this up a bit at some point.
21
- contract_class = constant || options["contract.#{name}.class"]
22
- model = options["model"] # FIXME: model.default
23
+ contract_class = constant || options["contract.#{name}.class"] # DISCUSS: Injection possible here?
24
+ model = options[:model]
23
25
  name = "contract.#{name}"
24
26
 
25
- return options[name] = Option::KW.(builder).(operation, options, constant: contract_class, name: name) if builder
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
33
+ end
34
+
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)
39
+
40
+
41
+
26
42
 
27
- options[name] = contract_class.new(model)
43
+
44
+
45
+
46
+
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
+
54
+ Trailblazer::Option(builder).( options, tmp_options, circuit_options )
28
55
  end
29
56
  end
30
57
 
31
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
32
63
  # This is the class level DSL method.
33
64
  # Op.contract #=> returns contract class
34
65
  # Op.contract do .. end # defines contract
@@ -0,0 +1,21 @@
1
+ module Trailblazer
2
+ class Operation
3
+ class DeprecatedOptions < Option
4
+ def self.call!(proc, direction, options, flow_options, *args)
5
+ if proc.is_a?(Proc) && proc.arity == 1
6
+ deprecate(proc)
7
+ proc.(options)
8
+ elsif proc.method(:call).arity == 1
9
+ deprecate(proc)
10
+ proc.(options)
11
+ else
12
+ super
13
+ end
14
+ end
15
+
16
+ def self.deprecate(proc)
17
+ warn "[Trailblazer] Please use the step API `def my_step!(options, **)` for your step: #{proc}"
18
+ end
19
+ end # DeprecatedOptions
20
+ end
21
+ end
@@ -1,17 +1,17 @@
1
- require "trailblazer/operation/policy"
2
-
3
1
  class Trailblazer::Operation
4
2
  module Policy
5
3
  def self.Guard(proc, name: :default, &block)
6
- Policy.step(Guard.build(proc), name: name)
4
+ Policy.step( Guard.build(proc), name: name )
7
5
  end
8
6
 
9
7
  module Guard
10
8
  def self.build(callable)
11
- value = Option::KW.(callable) # Operation::Option
9
+ option = Trailblazer::Option::KW(callable)
12
10
 
13
11
  # this gets wrapped in a Operation::Result object.
14
- ->(input, options) { Result.new( !!value.(input, options), {} ) }
12
+ ->( (options, *), circuit_args ) do
13
+ Result.new( !!option.(options, circuit_args), {} )
14
+ end
15
15
  end
16
16
  end # Guard
17
17
  end
@@ -1,23 +1,28 @@
1
1
  class Trailblazer::Operation
2
2
  def self.Model(model_class, action=nil)
3
- step = Model.for(model_class, action)
3
+ # step = Pipetree::Step.new(step, "model.class" => model_class, "model.action" => action)
4
4
 
5
- step = Pipetree::Step.new(step, "model.class" => model_class, "model.action" => action)
5
+ task = Railway::TaskBuilder.( Model.new )
6
6
 
7
- [ step, name: "model.build" ]
7
+ runner_options = {
8
+ merge: Wrap::Inject::Defaults(
9
+ "model.class" => model_class,
10
+ "model.action" => action
11
+ )
12
+ }
13
+
14
+ { task: task, id: "model.build", runner_options: runner_options }
8
15
  end
9
16
 
10
- module Model
11
- def self.for(model_class, action)
17
+ class Model
18
+ def call(options, params:, **)
12
19
  builder = Model::Builder.new
13
20
 
14
- ->(input, options) do
15
- options["model"] = model = builder.(options, options["params"])
21
+ options[:model] = model = builder.(options, params)
16
22
 
17
- options["result.model"] = result = Result.new(!model.nil?, {})
23
+ options["result.model"] = result = Result.new(!model.nil?, {})
18
24
 
19
- result.success?
20
- end
25
+ result.success?
21
26
  end
22
27
 
23
28
  class Builder
@@ -1,113 +1,84 @@
1
- class Trailblazer::Operation
2
- def self.Nested(callable, input:nil, output:nil)
3
- step = Nested.for(callable, input, output)
4
-
5
- [ step, { name: "Nested(#{callable})" } ]
6
- end
7
-
8
- # WARNING: this is experimental API, but it will end up with something like that.
9
- module Element
10
- # DISCUSS: add builders here.
11
- def initialize(wrapped=nil)
12
- @wrapped = wrapped
13
- end
1
+ # per default, everything we pass into a circuit is immutable. it's the ops/act's job to allow writing (via a Context)
2
+ module Trailblazer
3
+ class Operation
4
+ def self.Nested(callable, input:nil, output:nil, id: "Nested(#{callable})")
5
+ task_wrap_wirings = []
6
+ task, operation = Nested.build(callable, input, output)
14
7
 
15
- module Dynamic
16
- def initialize(wrapped)
17
- @wrapped = Option::KW.(wrapped)
18
- end
19
- end
20
- end
8
+ # @needs operation#outputs
21
9
 
22
- module Nested
23
- # Please note that the instance_variable_get are here on purpose since the
24
- # superinternal API is not entirely decided, yet.
25
- # @api private
26
- def self.for(step, input, output, is_nestable_object=method(:nestable_object?)) # DISCUSS: use builders here?
27
- invoker = Caller::Dynamic.new(step)
28
- invoker = Caller.new(step) if is_nestable_object.(step)
10
+ # TODO: move this to the generic step DSL
11
+ task_wrap_extensions = []
29
12
 
30
- options_for_nested = Options.new
31
- options_for_nested = Options::Dynamic.new(input) if input
13
+ if input || output
14
+ default_input_filter = ->(options, *) { ctx = options }
15
+ default_output_filter = ->(options, *) { options }
32
16
 
33
- options_for_composer = Options::Output.new
34
- options_for_composer = Options::Output::Dynamic.new(output) if output
17
+ input ||= default_input_filter
18
+ output ||= default_output_filter
35
19
 
36
- # This lambda is the strut added on the track, executed at runtime.
37
- ->(operation, options) do
38
- result = invoker.(operation, options, options_for_nested.(operation, options)) # TODO: what about containers?
20
+ input_filter = Activity::Wrap::Input.new(input)
21
+ output_filter = Activity::Wrap::Output.new(output)
39
22
 
40
- options_for_composer.(operation, options, result).each { |k,v| options[k] = v }
41
-
42
- result.success? # DISCUSS: what if we could simply return the result object here?
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
43
27
  end
44
- end
28
+ # Default {Output} copies the mutable data from the nested activity into the original.
45
29
 
46
- def self.nestable_object?(object)
47
- # interestingly, with < we get a weird nil exception. bug in Ruby?
48
- object.is_a?(Class) && object <= Trailblazer::Operation
30
+ { task: task, id: id, runner_options: { merge: task_wrap_extensions }, plus_poles: Activity::Magnetic::DSL::PlusPoles.from_outputs(operation.outputs) }
49
31
  end
50
32
 
51
- # Is executed at runtime and calls the nested operation.
52
- class Caller
53
- include Element
33
+ # @private
34
+ 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)
54
37
 
55
- def call(input, options, options_for_nested)
56
- call_nested(nested(input, options), options_for_nested)
38
+ # The returned {Nested} instance is a valid circuit element and will be `call`ed in the circuit.
39
+ # It simply returns the nested activity's `signal,options,flow_options` return set.
40
+ # 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
57
42
  end
58
43
 
59
- private
60
- def call_nested(operation, options)
61
- operation._call(options)
44
+ def self.nestable_object?(object)
45
+ object.is_a?( Trailblazer::Activity::Interface )
62
46
  end
63
47
 
64
- def nested(*); @wrapped end
65
-
66
- class Dynamic < Caller
67
- include Element::Dynamic
68
-
69
- def nested(input, options)
70
- @wrapped.(input, options)
71
- end
48
+ def self.operation_class
49
+ Operation
72
50
  end
73
- end
74
-
75
- class Options
76
- include Element
77
51
 
78
- # Per default, only runtime data for nested operation.
79
- def call(input, options)
80
- options.to_runtime_data[0]
81
- end
52
+ private
82
53
 
54
+ # For dynamic `Nested`s that do not expose an {Activity} interface.
55
+ # Since we do not know its outputs, we have to map them to :success and :failure, only.
56
+ #
57
+ # This is what {Nested} in 2.0 used to do, where the outcome could only be true/false (or success/failure).
83
58
  class Dynamic
84
- include Element::Dynamic
85
-
86
- def call(operation, options)
87
- @wrapped.(operation, options, runtime_data: options.to_runtime_data[0], mutable_data: options.to_mutable_data )
59
+ def initialize(wrapped)
60
+ @wrapped = Trailblazer::Option::KW(wrapped)
61
+ @outputs = {
62
+ :success => Activity::Output( Railway::End::Success.new(:success), :success ),
63
+ :failure => Activity::Output( Railway::End::Failure.new(:failure), :failure ),
64
+ }
88
65
  end
89
- end
90
66
 
91
- class Output
92
- include Element
67
+ attr_reader :outputs
93
68
 
94
- def call(input, options, result)
95
- mutable_data_for(result).each { |k,v| options[k] = v }
96
- end
97
-
98
- def mutable_data_for(result)
99
- result.instance_variable_get(:@data).to_mutable_data
100
- end
69
+ def call( (options, flow_options), **circuit_options )
70
+ activity = @wrapped.(options, circuit_options) # evaluate the option to get the actual "object" to call.
101
71
 
102
- class Dynamic < Output
103
- include Element::Dynamic
72
+ signal, args = activity.__call__( [options, flow_options], **circuit_options )
104
73
 
105
- def call(input, options, result)
106
- @wrapped.(input, options, mutable_data: mutable_data_for(result))
107
- end
74
+ # Translate the genuine nested signal to the generic Dynamic end (success/failure, only).
75
+ # 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
+ ]
108
80
  end
109
81
  end
110
82
  end
111
- end
83
+ end # Operation
112
84
  end
113
-