An operation or an activity provides two APIs: The wiring API focuses on defining the tasks and their connections. It is so versatile that it deserves its own document. The other part is about actually implementing those tasks (or “boxes”) and is called the operation API, and this document describes it.
As already discussed briefly, designing connections and tasks happens through the wiring API. The goal of the separation is that tasks can focus on one thing: implementing the actual business logic without having to worry about the control flow. To do so, they simply emit (or
return) special objects called signals, which are then translated into a connection. After this, the circuit engine will move on to the next task that is targeted by the connection.
Step API: KW-only
Some individuals fancy an alternative signature for steps where
options is not a positional argument, but just another keyword. This is also called the macaroni style.
class Memo::Create < Trailblazer::Operation # ... def create_model( params:, options:, ** ) options[:model] = Memo.new( title: params[:title] ) end def save( model:, ** ) model.save end end
Here, you don’t have to define
options since it’s just another keyword argument. If you need to set state, you can grab it using the
options: keyword (as done it
#create_model) but in “stateless” steps you can omit it (
The advantage is that you don’t need to define
options when you don’t need it. The downside is, it might be harder to explain that there’s a special state-transporting keyword argument, whereas it’s relatively easy to grasp this behavior with a positional argument.
You need to configure each step using the macaroni style with a custom normalizer.
class Memo::Create < Trailblazer::Operation Normalizer = Railway::Normalizer.new( task_builder: Railway::KwSignature ) step :create_model, normalizer: Normalizer step :save, normalizer: Normalizer # ... end
This can be easily abstracted into your
Primary Binary State
The primary state is decided by the activity’s end event superclass. If derived from
Railway::End::Success, it will be interpreted as successful, and
result.success? will return true, whereas a subclass of
Railway::End::Failure results in the opposite outcome. Here,
result.failure? is true.
Result: End Event
You can access the end event the Result wraps via
event. This allows to interpret the outcome on a finer level and without having to guess from data in the options context. (See Endpoint)
result = Create.( params ) result.event #=> #<Railway::FastTrack::PassFast ...>
For debugging or understanding the flows of activities, you can use tracing.
With operations, the simplest way is to use the
trace method. It has the exact same signature as
result = Create::Present.trace( params: params, current_user: current_user ) puts result.wtf? # => # |-- Start.default # |-- model.build # |-- contract.build # `-- End.success
Result#wtf? to render a simple view of all steps that were involved in the run.
Tracing starts to make things a lot easier for more complex, nested operations.
result = Create.trace( params: params, current_user: current_user ) puts result.wtf? # => # |-- #<Trailblazer::Activity::Nested:0x000000031960d8> # | |-- Start.default # | |-- model.build # | |-- contract.build # | |-- End.success # | `-- #<Trailblazer::Activity::Nested:0x000000031960d8> # |-- #<Trailblazer::Activity::Nested:0x0000000311f3e8> # | |-- Start.default # | |-- contract.default.params # | |-- End.failure # | `-- #<Trailblazer::Activity::Nested:0x0000000311f3e8> # `-- #<Trailblazer::Operation::Railway::End::Failure:0x00000003201fb8>
Please refer to the activity docs for a low-level interface tracing.
In future versions, tracing will also display variables with improved configurability. This will be added very very shortly.
Very soon, you can debug your code in real-time visually using the PRO editor.