Trailblazer 2.1.2 bringing the Each() macro.
Published: 2022-12-18 08:49:10

Recently we released a bunch of updated Trailblazer gems and along with those the `trailblazer` framework 2.1.2.

## Loops in Operation

It felt great to close [several](https://github.com/trailblazer/trailblazer-macro/pull/38) [pull requests](https://github.com/trailblazer/trailblazer/issues/245) that were asking for (or even adding) support to iterate over datasets in TRB, basically an `each do ... end` for operations.

Integrating the features added by our fellow users with internal advanced mechanics such as tracing or patching took its time, that's why the "simple" PR sat there quite a while.

```ruby
module Song::Operation
  class Cover < Trailblazer::Operation
    step :model
    step Each(dataset_from: :composers_for_each) {
      step :notify_composers
      # more steps here or Subprocess()
    }
    step :rearrange

    def composers_for_each(ctx, model:, **)
      model.composers
    end
    # ...
  end
end
```

The `Each()` macro allows you to specify where the data set is coming from, and will then run the provided block (or another operation) for each item at runtime. 

Assuming we had three `composers` in the data set when we invoke the operation, the nested code is run subsequently as if there were three dedicated `notify_composers` steps.

```ruby
class Cover < Trailblazer::Operation
  step :model
  step :notify_composers.0
  step :notify_composers.1
  step :notify_composers.2
  step :rearrange
```

We added features such as collecting values from the iterated block or input/output filtering, all described in [the newly added docs](https://trailblazer.to/2.1/docs/macro#macro-each).

Speaking of, we moved the macro docs to an [entirely new section](https://trailblazer.to/2.1/docs/macro#macro-overview), and rewrote docs for `Wrap()` as they were pretty basic before.

One thing I particularly love about this new macro is that it integrates nicely with our debugging API. You can even see in which iteration an error occurred! Check the trace below.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vw9fnunz9dv7nb58x2yh.png)

Using Ruby's "native" `each`, this'd be a PITA to debug.

[Your input](https://trailblazer.zulipchat.com/#) on `Each()` is crucial - feel free to chat to us at any time.

## Composable input/output

In former versions of Trailblazer, we had the `:input` and `:output` option to define what comes in and goes out of a step.

```ruby
class Create < Trailblazer::Operation
  # ...
  step :validate,
    input: [:params],  # pass :params into the step
    output: [:result]  # only let :result out!
end
```

Variable mapping - as this is called in Trailblazer - is, after nesting, the most commonly used feature of the framework.

Good news: we are not dropping support for `:input` and `:output`! However, this is now superseded by "composable" variable mapping. On first glance, nothing much has changed.

```ruby
step :validate,
    In() => [:params], 
    Out() => [:result]
```

Well, we replaced the `:symbol` option with `In()`, `Out()`, and `Inject()`. However, this comes in handy if you want to add or alter variable mapping that has been defined formerly, for example in a macro.

```ruby
step Validate(), # adds In() and maybe Inject()
  In() => [:current_user] # we can add this on top!
```
You can even override and alter [inherited variable mapping](https://trailblazer.to/2.1/docs/activity.html#activity-variable-mapping-inheritance) in a subclass.

```ruby
class Upsert < Create
  step :validate_for_upsert, 
    replace: :validate,  # override inherited #validate step.
    inherit: [:variable_mapping], # inherit filters from Create.
    Inject() => [:current_user] # add this on top.
```

The entire approach with composable filters is extremely flexible and helps reducing bulky input/output code.

### Introspect your filter chains

While this might look like a syntax touch-up, and, yes, _anoooother_ API change, this brings us not only better structural features, but we can now introspect filter chains with the `developer` gem.

```ruby
puts Trailblazer::Developer.render(Create, :validate)
```

This renders the taskWrap and along with it the input and output filter chain. We even combine that with our upcoming IDE.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h3w2l02diw508818w4ng.png)

Note that this is not the final introspect API, though!

The code for variable mapping sits in the [`dsl-linear` gem](https://github.com/trailblazer/trailblazer-activity-dsl-linear/blob/9cdcf1efe5e656a2e586e3159d0b92b054b9da2c/lib/trailblazer/activity/dsl/linear/feature/variable_mapping.rb#L125) and got some love, again. It is much easier to follow (and about 1/3 faster!) so maybe have a peek and add whatever you're missing?

You can find [brand-new docs on our website](https://trailblazer.to/2.1/docs/activity.html#activity-variable-mapping).

## Debugger and IDE

When working on Reform 3 and `Each()` I used [Trailblazer's debugging tools](https://trailblazer.to/2.1/docs/trailblazer.html#trailblazer-developer) frequently and while benefiting from `wtf?` and friends, we decided to improve the internal debugging API. 

This turned into a bare-bones IDE that allows you to traverse through the trace of a deeply nested operation.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8vso8czei3xckivar403.png)


You can trace the incoming and outgoing ctx for each step, and even inspect its compiled taskWrap - where you can see the variable mapping chain!

The IDE will be a service launched by us in 2023.

## What's next

Don't get me started, I could keep talking for hours! Anyhow, let's wrap this up.

* [Reform 3 is coming](https://www.youtube.com/watch?v=vNAUZ0rUcow), we're pretty close to a presentable version. It is entirely built with Trailblazer, so tracing, variable mapping, extendability and all things TRB come for free!
* The `endpoint` gem will get a stable release, promised!
* More [Trailblazer Tales](https://www.youtube.com/channel/UCi2P0tFMtjMUsWLYAD1Ezsw) are planned, sorry for the long interruption! Next topics will be nesting, variable mapping and the Wiring API.

We're looking forward to your feedback! I deleted Twitter and [live on Mastodon](https://ruby.social/@apotonick) now. Cheers and happy holidays!