trailblazer 2.0.7 → 2.1.0.beta1

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