Operation 2.1 API

Last updated 20 December 2017 trailblazer-operation v2.1


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.

Control Flow

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.

Task API

Step API

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] = title: params[:title] )

  def save( model:, ** )

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 (#save).

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 = task_builder: Railway::KwSignature )

  step :create_model, normalizer: Normalizer
  step :save,         normalizer: Normalizer
  # ...

This can be easily abstracted into your Application::Operation.


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 call.

result = Create::Present.trace( params: params, current_user: current_user )

# =>
# |-- Start.default
# |--
# |--
# `-- End.success

Use 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 )

# =>
# |-- #<Trailblazer::Activity::Nested:0x000000031960d8>
# |   |-- Start.default
# |   |--
# |   |--
# |   |-- 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.