trailblazer-macro 2.1.10.beta1 → 2.1.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,81 +1,88 @@
1
- require 'securerandom'
2
-
3
1
  module Trailblazer
4
2
  module Macro
5
- def self.Wrap(user_wrap, id: "Wrap/#{SecureRandom.hex(4)}", &block)
6
- activity = Class.new(Activity::FastTrack, &block) # This is currently coupled to {dsl-linear}.
7
-
8
- outputs = activity.to_h[:outputs]
9
- outputs = Hash[outputs.collect { |output| [output.semantic, output] }] # TODO: make that a helper somewhere.
10
-
11
- wrapped = Wrap::Wrapped.new(activity, user_wrap, outputs)
3
+ # TODO: {user_wrap}: rename to {wrap_handler}.
4
+ def self.Wrap(user_wrap, id: Macro.id_for(user_wrap, macro: :Wrap), &block)
5
+ user_wrap = Wrap.deprecate_positional_wrap_signature(user_wrap)
6
+
7
+ block_activity, outputs = Macro.block_activity_for(nil, &block)
8
+
9
+ outputs = Hash[outputs.collect { |output| [output.semantic, output] }] # FIXME: redundant to Subprocess().
10
+
11
+ # Since in the user block, you can return Railway.pass! etc, we need to map
12
+ # those to the actual wrapped block_activity's end.
13
+ signal_to_output = {
14
+ Activity::Right => outputs[:success].signal,
15
+ Activity::Left => outputs[:failure].signal,
16
+ Activity::FastTrack::PassFast => outputs[:pass_fast].signal,
17
+ Activity::FastTrack::FailFast => outputs[:fail_fast].signal,
18
+ true => outputs[:success].signal,
19
+ false => outputs[:failure].signal,
20
+ nil => outputs[:failure].signal,
21
+ }
22
+
23
+ state = Declarative::State(
24
+ # this is important, so we subclass the actually wrapped activity when {Wrap} is subclassed.
25
+ block_activity: [block_activity, {copy: Trailblazer::Declarative::State.method(:subclass)}],
26
+ user_wrap: [user_wrap, {}], # DISCUSS: we could even allow the wrap_handler to be patchable.
27
+ signal_to_output: [signal_to_output, {}],
28
+ )
29
+
30
+ task = Class.new(Wrap) do
31
+ extend Macro::Strategy::State # now, the Wrap subclass can inherit its state and copy the {block_activity}.
32
+ initialize!(state)
33
+ end
34
+ # DISCUSS: unfortunately, Ruby doesn't allow to set this during {Class.new}.
12
35
 
13
- {task: wrapped, id: id, outputs: outputs}
36
+ {
37
+ task: task,
38
+ id: id,
39
+ outputs: outputs,
40
+ }
14
41
  end
15
42
 
16
- module Wrap
43
+ # Wrap exposes {#inherited} which will also copy the block activity.
44
+ # Currently, this is only used for patching (as it will try to subclass Wrap).
45
+ class Wrap < Macro::Strategy # TODO: it would be cool to have Activity::Interface and Strategy::Interface
17
46
  # behaves like an operation so it plays with Nested and simply calls the operation in the user-provided block.
18
- class Wrapped
19
- private def deprecate_positional_wrap_signature(user_wrap)
20
- parameters = user_wrap.is_a?(Module) ? user_wrap.method(:call).parameters : user_wrap.parameters
47
+ # class Wrapped
48
+ # @private
49
+ def self.deprecate_positional_wrap_signature(user_wrap)
50
+ parameters = user_wrap.is_a?(Proc) || user_wrap.is_a?(Method) ? user_wrap.parameters : user_wrap.method(:call).parameters
21
51
 
22
- return user_wrap if parameters[0] == [:req] # means ((ctx, flow_options), *, &block), "new style"
52
+ return user_wrap if parameters[0] == [:req] # means ((ctx, flow_options), *, &block), "new style"
23
53
 
24
- ->((ctx, flow_options), **circuit_options, &block) do
25
- warn "[Trailblazer] Wrap handlers have a new signature: ((ctx), *, &block)"
26
- user_wrap.(ctx, &block)
27
- end
54
+ ->((ctx, flow_options), **circuit_options, &block) do
55
+ warn "[Trailblazer] Wrap handlers have a new signature: ((ctx), *, &block) XXX"
56
+ user_wrap.(ctx, &block)
28
57
  end
58
+ end
29
59
 
30
- def initialize(operation, user_wrap, outputs)
31
- user_wrap = deprecate_positional_wrap_signature(user_wrap)
32
-
33
- @operation = operation
34
- @user_wrap = user_wrap
35
-
36
- # Since in the user block, you can return Railway.pass! etc, we need to map
37
- # those to the actual wrapped operation's end.
38
- @signal_to_output = {
39
- Operation::Railway.pass! => outputs[:success].signal,
40
- Operation::Railway.fail! => outputs[:failure].signal,
41
- Operation::Railway.pass_fast! => outputs[:pass_fast].signal,
42
- Operation::Railway.fail_fast! => outputs[:fail_fast].signal,
43
- true => outputs[:success].signal,
44
- false => outputs[:failure].signal,
45
- nil => outputs[:failure].signal,
46
- }
60
+ def self.call((ctx, flow_options), **circuit_options)
61
+ # since yield is called without arguments, we need to pull default params from here. Oh ... tricky.
62
+ block_calling_wrapped = ->(args=[ctx, flow_options], kwargs=circuit_options) {
63
+ Activity::Circuit::Runner.(block_activity, args, **kwargs)
64
+ }
65
+
66
+ # call the user's Wrap {} block in the operation.
67
+ # This will invoke block_calling_wrapped above if the user block yields.
68
+ returned = @state.get(:user_wrap).([ctx, flow_options], **circuit_options, &block_calling_wrapped)
69
+
70
+ # {returned} can be
71
+ # 1. {circuit interface return} from the begin block, because the wrapped OP passed
72
+ # 2. {task interface return} because the user block returns "customized" signals, true of fale
73
+
74
+ if returned.is_a?(Array) # 1. {circuit interface return}, new style.
75
+ signal, (ctx, flow_options) = returned
76
+ else # 2. {task interface return}, only a signal (or true/false)
77
+ # TODO: deprecate this?
78
+ signal = returned
47
79
  end
48
80
 
49
- def call((ctx, flow_options), **circuit_options)
50
- block_calling_wrapped = -> {
51
- call_wrapped_activity([ctx, flow_options], **circuit_options)
52
- }
81
+ # If there's no mapping, use the original {signal} .
82
+ # This usually means signal is a terminus or a custom signal.
83
+ signal = @state.get(:signal_to_output).fetch(signal, signal)
53
84
 
54
- # call the user's Wrap {} block in the operation.
55
- # This will invoke block_calling_wrapped above if the user block yields.
56
- returned = @user_wrap.([ctx, flow_options], **circuit_options, &block_calling_wrapped)
57
-
58
- # {returned} can be
59
- # 1. {circuit interface return} from the begin block, because the wrapped OP passed
60
- # 2. {task interface return} because the user block returns "customized" signals, true of fale
61
-
62
- if returned.is_a?(Array) # 1. {circuit interface return}, new style.
63
- signal, (ctx, flow_options) = returned
64
- else # 2. {task interface return}, only a signal (or true/false)
65
- # TODO: deprecate this?
66
- signal = returned
67
- end
68
-
69
- # Use the original {signal} if there's no mapping.
70
- # This usually means signal is an End instance or a custom signal.
71
- signal = @signal_to_output.fetch(signal, signal)
72
-
73
- return signal, [ctx, flow_options]
74
- end
75
-
76
- def call_wrapped_activity((ctx, flow_options), **circuit_options)
77
- @operation.to_h[:activity].([ctx, flow_options], **circuit_options) # :exec_context is this instance.
78
- end
85
+ return signal, [ctx, flow_options]
79
86
  end
80
87
  end # Wrap
81
88
  end
@@ -2,6 +2,7 @@ require "forwardable"
2
2
  require "trailblazer/activity/dsl/linear"
3
3
  require "trailblazer/operation" # TODO: remove this dependency
4
4
 
5
+ require "trailblazer/macro/strategy"
5
6
  require "trailblazer/macro/model"
6
7
  require "trailblazer/macro/policy"
7
8
  require "trailblazer/macro/guard"
@@ -9,10 +10,60 @@ require "trailblazer/macro/pundit"
9
10
  require "trailblazer/macro/nested"
10
11
  require "trailblazer/macro/rescue"
11
12
  require "trailblazer/macro/wrap"
13
+ require "trailblazer/macro/each"
12
14
 
13
15
  module Trailblazer
14
16
  module Macro
15
- end
17
+ # TaskAdapter::AssignVariable
18
+ # Run {user_proc} with "step interface" and assign its return value to ctx[@variable_name].
19
+ # @private
20
+ # This is experimental.
21
+ class AssignVariable
22
+ # name of the ctx variable we want to assign the return_value of {user_proc} to.
23
+ def initialize(return_value_step, variable_name:)
24
+ @return_value_step = return_value_step
25
+ @variable_name = variable_name
26
+ end
27
+
28
+ def call((ctx, flow_options), **circuit_options)
29
+ return_value, ctx = @return_value_step.([ctx, flow_options], **circuit_options)
30
+
31
+ ctx[@variable_name] = return_value
32
+
33
+ return return_value, ctx
34
+ end
35
+ end
36
+
37
+ def self.task_adapter_for_decider(decider_with_step_interface, variable_name:)
38
+ return_value_circuit_step = Activity::Circuit.Step(decider_with_step_interface, option: true)
39
+
40
+ assign_task = AssignVariable.new(return_value_circuit_step, variable_name: variable_name)
41
+
42
+ Activity::Circuit::TaskAdapter.new(assign_task) # call {assign_task} with circuit-interface, interpret result.
43
+ end
44
+
45
+ def self.block_activity_for(block_activity, &block)
46
+ return block_activity, block_activity.to_h[:outputs] unless block_given?
47
+
48
+ block_activity = Class.new(Activity::FastTrack, &block) # TODO: use Wrap() logic!
49
+ block_activity.extend Each::Transitive
50
+
51
+ return block_activity, block_activity.to_h[:outputs]
52
+ end
53
+
54
+ def self.id_for(user_proc, macro:, hint: nil)
55
+ id =
56
+ if user_proc.is_a?(Class)
57
+ user_proc.to_s
58
+ elsif user_proc.instance_of?(Method)
59
+ "method(:#{user_proc.name})"
60
+ else
61
+ hint || rand(4)
62
+ end
63
+
64
+ "#{macro}/#{id}"
65
+ end
66
+ end # Macro
16
67
 
17
68
  # All macros sit in the {Trailblazer::Macro} namespace, where we forward calls from
18
69
  # operations and activities to.
@@ -23,6 +74,6 @@ module Trailblazer
23
74
  # Extending the {Linear::Helper} namespace is the canonical way to import
24
75
  # macros into Railway, FastTrack, Operation, etc.
25
76
  extend Forwardable
26
- def_delegators Trailblazer::Macro, :Model, :Nested, :Wrap, :Rescue
77
+ def_delegators Trailblazer::Macro, :Model, :Nested, :Wrap, :Rescue, :Each
27
78
  end # Helper
28
79
  end