DEV Community

Nick Sutterer for Trailblazer

Posted on

Trailblazer 2.1.2 bringing the Each() macro.

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 pull requests 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.

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
Enter fullscreen mode Exit fullscreen mode

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.

class Cover < Trailblazer::Operation
  step :model
  step :notify_composers.0
  step :notify_composers.1
  step :notify_composers.2
  step :rearrange
Enter fullscreen mode Exit fullscreen mode

We added features such as collecting values from the iterated block or input/output filtering, all described in the newly added docs.

Speaking of, we moved the macro docs to an entirely new section, 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

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

Your input 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.

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

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.

step :validate,
    In() => [:params], 
    Out() => [:result]
Enter fullscreen mode Exit fullscreen mode

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.

step Validate(), # adds In() and maybe Inject()
  In() => [:current_user] # we can add this on top!
Enter fullscreen mode Exit fullscreen mode

You can even override and alter inherited variable mapping in a subclass.

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.
Enter fullscreen mode Exit fullscreen mode

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.

puts Trailblazer::Developer.render(Create, :validate)
Enter fullscreen mode Exit fullscreen mode

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

Image description

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

The code for variable mapping sits in the dsl-linear gem 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.

Debugger and IDE

When working on Reform 3 and Each() I used Trailblazer's debugging tools 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

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, 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 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 now. Cheers and happy holidays!

Top comments (0)