Module: Trailblazer::Macro
- Defined in:
- lib/trailblazer/macro.rb,
lib/trailblazer/macro/each.rb,
lib/trailblazer/macro/wrap.rb,
lib/trailblazer/macro/guard.rb,
lib/trailblazer/macro/model.rb,
lib/trailblazer/macro/nested.rb,
lib/trailblazer/macro/policy.rb,
lib/trailblazer/macro/pundit.rb,
lib/trailblazer/macro/rescue.rb,
lib/trailblazer/macro/strategy.rb,
lib/trailblazer/macro/model/find.rb
Defined Under Namespace
Modules: IdFor, Policy, Rescue Classes: AssignVariable, Each, Model, Nested, Strategy, Wrap
Constant Summary collapse
- NoopHandler =
lambda { |*| }
Class Method Summary collapse
- .block_activity_for(block_activity, &block) ⇒ Object
- .Each(block_activity = nil, dataset_from: nil, item_key: :item, id: Macro.id_for(block_activity, macro: :Each, hint: dataset_from), collect: false, **dsl_options_for_iterated, &block) ⇒ Object
- .id_for(user_proc, **options) ⇒ Object
- .Model(model_class = nil, action = :new, find_by_key = :id, id: 'model.build', not_found_terminus: false) ⇒ Object
-
.Nested(callable, id: Macro.id_for(callable, macro: :Nested, hint: callable), auto_wire: []) ⇒ Object
Nested macro.
-
.options_for_collect(collect:) ⇒ Object
DSL options added to block_activity to implement true.
- .options_for_dataset_from(dataset_from:) ⇒ Object
- .Rescue(*exceptions, handler: NoopHandler, id: Rescue.random_id, &block) ⇒ Object
- .task_adapter_for_decider(decider_with_step_interface, variable_name:) ⇒ Object
- .task_wrap_for_iterated(dsl_options) ⇒ Object
-
.Wrap(user_wrap, id: Macro.id_for(user_wrap, macro: :Wrap), &block) ⇒ Object
TODO: user_wrap: rename to wrap_handler.
Class Method Details
.block_activity_for(block_activity, &block) ⇒ Object
46 47 48 49 50 51 52 53 |
# File 'lib/trailblazer/macro.rb', line 46 def self.block_activity_for(block_activity, &block) return block_activity, block_activity.to_h[:outputs] unless block_given? block_activity = Class.new(Activity::FastTrack, &block) # TODO: use Wrap() logic! block_activity.extend Each::Transitive return block_activity, block_activity.to_h[:outputs] end |
.Each(block_activity = nil, dataset_from: nil, item_key: :item, id: Macro.id_for(block_activity, macro: :Each, hint: dataset_from), collect: false, **dsl_options_for_iterated, &block) ⇒ Object
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/trailblazer/macro/each.rb', line 85 def self.Each(block_activity=nil, dataset_from: nil, item_key: :item, id: Macro.id_for(block_activity, macro: :Each, hint: dataset_from), collect: false, **, &block) = block_activity if block_activity.is_a?(Hash) # Ruby 2.5 and 2.6 block_activity, outputs_from_block_activity = Macro.block_activity_for(block_activity, &block) = (collect: collect) = (dataset_from: dataset_from) wrap_static_for_block_activity = task_wrap_for_iterated( {Activity::Railway.Out() => []}. # per default, don't let anything out. merge(). merge() ) # This activity is passed into the {Runner} for each iteration of {block_activity}. container_activity = Activity::TaskWrap.container_activity_for( block_activity, id: "invoke_block_activity", # merged into {:config}: each: true, # mark this activity for {compute_runtime_id}. ).merge( outputs: outputs_from_block_activity, ) # FIXME: we can't pass {wrap_static: wrap_static_for_block_activity} into {#container_activity_for} # because when patching, the container_activity is not recompiled, so we need the Hash here # with defaulting. # FIXME: this "hack" is only here to satify patching. config = container_activity[:config].merge(wrap_static: Hash.new(wrap_static_for_block_activity)) container_activity.merge!(config: config) # DISCUSS: move to Wrap. termini_from_block_activity = outputs_from_block_activity. # DISCUSS: End.success needs to be the last here, so it's directly behind {Start.default}. sort { |a,b| a.semantic == :success ? 1 : -1 }. collect { |output| [output.signal, id: "End.#{output.semantic}", magnetic_to: output.semantic, append_to: "Start.default"] } state = Declarative::State( block_activity: [block_activity, {copy: Trailblazer::Declarative::State.method(:subclass)}], # DISCUSS: move to Macro::Strategy. item_key: [item_key, {}], # DISCUSS: we could even allow the wrap_handler to be patchable. failing_semantic: [[:failure, :fail_fast], {}], activity: [container_activity, {}], success_signal: [termini_from_block_activity[-1][0], {}] # FIXME: when subclassing (e.g. patching) this must be recomputed. ) # |-- Each/composers_for_each # | |-- Start.default # | |-- Each.iterate.block This is Class.new(Each), outputs_from_block_activity # | | |-- invoke_block_activity.0 step :invoke_block_activity.0 # | | | |-- Start.default # | | | |-- notify_composers # | | | `-- End.success # | | `-- invoke_block_activity.1 step "invoke_block_activity.1" # | | |-- Start.default # | | |-- notify_composers iterate_strategy = Class.new(Each) do extend Macro::Strategy::State # now, the Wrap subclass can inherit its state and copy the {block_activity}. initialize!(state) end each_activity = Activity::FastTrack(termini: termini_from_block_activity) # DISCUSS: what base class should we be using? each_activity.extend Each::Transitive # {Subprocess} with {strict: true} will automatically wire all {block_activity}'s termini to the corresponding termini # of {each_activity} as they have the same semantics (both termini sets are identical). each_activity.step Activity::Railway.Subprocess(iterate_strategy, strict: true), id: "Each.iterate.#{block ? :block : block_activity}" # FIXME: test :id. Activity::Railway.Subprocess(each_activity). merge(id: id). merge() # FIXME: provide that service via Subprocess. end |
.id_for(user_proc, **options) ⇒ Object
73 74 75 |
# File 'lib/trailblazer/macro.rb', line 73 def self.id_for(user_proc, **) IdFor.(user_proc, **) end |
.Model(model_class = nil, action = :new, find_by_key = :id, id: 'model.build', not_found_terminus: false) ⇒ Object
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/trailblazer/macro/model.rb', line 4 def self.Model(model_class = nil, action = :new, find_by_key = :id, id: 'model.build', not_found_terminus: false) task = Activity::Circuit::TaskAdapter.for_step(Model.new) injections = { Activity::Railway.Inject() => [:params], # pass-through {:params} if it's in ctx. # defaulting as per Inject() API. Activity::Railway.Inject() => { :"model.class" => ->(*) { model_class }, :"model.action" => ->(*) { action }, :"model.find_by_key" => ->(*) { find_by_key }, } } = {task: task, id: id}.merge(injections) = .merge(Activity::Railway.Output(:failure) => Activity::Railway.End(:not_found)) if not_found_terminus end |
.Nested(callable, id: Macro.id_for(callable, macro: :Nested, hint: callable), auto_wire: []) ⇒ Object
Nested macro. DISCUSS: rename auto_wire => static
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/trailblazer/macro/nested.rb', line 5 def self.Nested(callable, id: Macro.id_for(callable, macro: :Nested, hint: callable), auto_wire: []) # Warn developers when they confuse Nested with Subprocess (for simple nesting, without a dynamic decider). if callable.is_a?(Class) && callable < Nested.operation_class caller_locations = caller_locations(1, 2) caller_location = caller_locations[0].to_s =~ /forwardable/ ? caller_locations[1] : caller_locations[0] Activity::Deprecate.warn caller_location, "Using the `Nested()` macro without a dynamic decider is deprecated.\n" \ "To simply nest an activity or operation, replace `Nested(#{callable})` with `Subprocess(#{callable})`.\n" \ "Check the Subprocess API docs to learn more about nesting: https://trailblazer.to/2.1/docs/activity.html#activity-wiring-api-subprocess" return Activity::Railway.Subprocess(callable) end task = if auto_wire.any? Nested.Static(callable, id: id, auto_wire: auto_wire) else # no {auto_wire} Nested.Dynamic(callable, id: id) end # |-- Nested.compute_nested_activity...Trailblazer::Macro::Nested::Decider # `-- task_wrap.call_task..............Method merge = [ [Nested::Decider.new(callable), id: "Nested.compute_nested_activity", prepend: "task_wrap.call_task"], ] task_wrap_extension = Activity::TaskWrap::Extension::WrapStatic.new(extension: Activity::TaskWrap::Extension(*merge)) Activity::Railway.Subprocess(task).merge( # FIXME: allow this directly in Subprocess id: id, extensions: [task_wrap_extension], ) end |
.options_for_collect(collect:) ⇒ Object
DSL options added to block_activity to implement true.
171 172 173 174 175 176 177 178 |
# File 'lib/trailblazer/macro/each.rb', line 171 def self.(collect:) return {} unless collect { Activity::Railway.Inject(:collected_from_each) => ->(ctx, **) { [] }, # this is called only once. Activity::Railway.Out() => ->(ctx, collected_from_each:, **) { {collected_from_each: collected_from_each += [ctx[:value]] } } } end |
.options_for_dataset_from(dataset_from:) ⇒ Object
180 181 182 183 184 185 186 |
# File 'lib/trailblazer/macro/each.rb', line 180 def self.(dataset_from:) return {} unless dataset_from { Activity::Railway.Inject(:dataset, override: true) => dataset_from, # {ctx[:dataset]} is private to {each_activity}. } end |
.Rescue(*exceptions, handler: NoopHandler, id: Rescue.random_id, &block) ⇒ Object
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/trailblazer/macro/rescue.rb', line 5 def self.Rescue(*exceptions, handler: NoopHandler, id: Rescue.random_id, &block) exceptions = [StandardError] unless exceptions.any? handler = Rescue.deprecate_positional_handler_signature(handler) handler = Trailblazer::Option(handler) # This block is evaluated by {Wrap}. rescue_block = ->((ctx, ), **, &nested_activity) do begin nested_activity.call rescue *exceptions => exception # DISCUSS: should we deprecate this signature and rather apply the Task API here? handler.call(exception, ctx, **) # FIXME: when there's an error here, it shows the wrong exception! [Operation::Railway.fail!, [ctx, ]] end end Wrap(rescue_block, id: id, &block) end |
.task_adapter_for_decider(decider_with_step_interface, variable_name:) ⇒ Object
38 39 40 41 42 43 44 |
# File 'lib/trailblazer/macro.rb', line 38 def self.task_adapter_for_decider(decider_with_step_interface, variable_name:) return_value_circuit_step = Activity::Circuit.Step(decider_with_step_interface, option: true) assign_task = AssignVariable.new(return_value_circuit_step, variable_name: variable_name) Activity::Circuit::TaskAdapter.new(assign_task) # call {assign_task} with circuit-interface, interpret result. end |
.task_wrap_for_iterated(dsl_options) ⇒ Object
161 162 163 164 165 166 167 168 |
# File 'lib/trailblazer/macro/each.rb', line 161 def self.task_wrap_for_iterated() # TODO: maybe the DSL API could be more "open" here? I bet it is, but I'm too lazy. activity = Class.new(Activity::Railway) do step({task: "iterated"}.merge()) end activity.to_h[:config][:wrap_static]["iterated"] end |
.Wrap(user_wrap, id: Macro.id_for(user_wrap, macro: :Wrap), &block) ⇒ Object
TODO: user_wrap: rename to wrap_handler.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/trailblazer/macro/wrap.rb', line 4 def self.Wrap(user_wrap, id: Macro.id_for(user_wrap, macro: :Wrap), &block) user_wrap = Wrap.deprecate_positional_wrap_signature(user_wrap) block_activity, outputs = Macro.block_activity_for(nil, &block) outputs = Hash[outputs.collect { |output| [output.semantic, output] }] # FIXME: redundant to Subprocess(). # Since in the user block, you can return Railway.pass! etc, we need to map # those to the actual wrapped block_activity's end. signal_to_output = { Activity::Right => outputs[:success].signal, Activity::Left => outputs[:failure].signal, Activity::FastTrack::PassFast => outputs[:pass_fast].signal, Activity::FastTrack::FailFast => outputs[:fail_fast].signal, true => outputs[:success].signal, false => outputs[:failure].signal, nil => outputs[:failure].signal, } state = Declarative::State( # this is important, so we subclass the actually wrapped activity when {Wrap} is subclassed. block_activity: [block_activity, {copy: Trailblazer::Declarative::State.method(:subclass)}], user_wrap: [user_wrap, {}], # DISCUSS: we could even allow the wrap_handler to be patchable. signal_to_output: [signal_to_output, {}], ) task = Class.new(Wrap) do extend Macro::Strategy::State # now, the Wrap subclass can inherit its state and copy the {block_activity}. initialize!(state) end # DISCUSS: unfortunately, Ruby doesn't allow to set this during {Class.new}. { task: task, id: id, outputs: outputs, } end |