{{ page.title }}

  • Last updated 05 May 2017
  • representable v3.0

Trailblazer

  • Last updated 19 Oct 19

About

The Trailblazer project started off with a single gem called trailblazer. Currently, the framework consists of around 40 gems. The main gem trailblazer is an umbrella gem with the sole reason to pull all default dependencies for you. Feel free to pick and choose what you need for your applications.

Overview

The following list of gems is an overview of the most important core components in Trailblazer.

Gem Summary Dependencies
CORE
trailblazer-activity Callable run-time circuits that control the execution flow of your tasks.
trailblazer-activity-dsl-linear DSL that provides Path, Railway and FastTrack activities.
trailblazer-context Option and context implementation.
HELPER
trailblazer-operation Thin API around FastTrack exposing the "old" call-API named Operation.
trailblazer-macro Provides Nested(), Model() and other macros for everyday usage.
trailblazer-macro-contract Provides the Contract::Validate() macro and friends. reform, dry-validation
TOOLS
trailblazer-developer Activity visualization, tracing, debugging, PRO Editor communication.
trailblazer-test Minitest assertions and tools for fast TRB testing.
trailblazer-rspec Rspec testing extensions.
PRO
trailblazer-workflow Long-running activities with process engine, events and collaboration.
trailblazer-activity-implementation DSL for creating activities from PRO Editor exports.

Along with the core gems goes a rich list of eco-system gems.

Gem Summary Dependencies
CORE
reform Form objects.
cells Generic view components.
trailblazer-cells TRB-style view components.

Rails

Trailblazer runs with any Ruby web framework. However, if you’re using Rails, you’re lucky since we provide convenient glue code in the trailblazer-rails gem.


gem "trailblazer-rails"

todo: add versioning information

Loader

The trailblazer-loader gem implements a very simple way to load all files in your concepts directory in a heuristically meaningful order. It can be used in any environment.

The trailblazer-loader gem comes pre-bundled with trailblazer-rails for historical reasons: in the early days of Trailblazer, the conventional file name concepts/product/operation/create.rb didn’t match the short operation name, such as Product::Create.

The trailblazer-loader gem’s duty was to load all concept files without using Rails’ autoloader, overcoming the latter’s conventions.

Over the years, and with the emerge of controller helpers or our workflow engine calling operations for you, the class name of an operation more and becomes a thing not to worry about.

Many projects use Trailblazer along with the Rails naming convention now. This means you can disable the loader gem, and benefit from Rails auto-magic behavior such as faster loading in the “correct” order, reloading and all the flaws that come with this non-deterministic behavior.

As a first step, add Operation to your operation’s class name, matching the Rails naming convention.


# app/concepts/product/operation/create.rb

module Product::Operation
  class Create < Trailblazer::Operation
    # ...
  end
end

It’s a Trailblazer convention to put [ConceptName]::Operation in one line: it will force Rails to load the concept name constant, so you don’t have to reopen the class yourself.

This will result in a class name Product::Operation::Create.

Next, disable the loader gem, in config/initializers/trailblazer.rb.


# config/initializers/trailblazer.rb

YourApp::Application.config.trailblazer.enable_loader = false

Trailblazer files will now be loaded by Rails - you need to follow the Rails autoloading file naming from here on, and things should run smoothly. A nice side-effect here is that in bigger projects (with hundreds of operations), the start-up time in development accelerates significantly.

The infamous warning: toplevel constant Cell referenced by Notification::Cell warning is a bug in Ruby. You should upgrade to Ruby >= 2.5.

2.1 Migration

Call API

In versions before 2.1, the automatic merging of the params part and the additional options was confusing many new users and an unnecessary step.


# old style
result = Memo::Create.( params, "current_user" => current_user )

The first argument (params) was merged into the second argument using the key “params”. You now pass one hash to call and hence do the merging yourself.


# new style
result = Memo::Create.( params: params, current_user: current_user )

Your steps use the existing API, and everything here is as it used to be before.


class Memo::Create < Trailblazer::Operation
  step :create_model

  def create_model(options, params:, **)
    # ..
  end
end

The new call API is much more consistent and takes away another thing we kept explaining to new users - an indicator for a flawed API.

For a soft deprecation, do this in an initializer:


require "trailblazer/deprecation/call"

You will get a bunch of warnings, so fix all Operation.() calls and remove the require again.

Context

The keys for ctx used to be mixed up, some where "longer.strings", some were :symbols. The new context implementation Context::IndifferentAccess now allows to use both.


ctx["model"]
ctx[:model]  # => same!

This also works for namespaced keys, which you still might find helpful.


ctx["contract.default"]
ctx[:"contract.default"]  # => same!

On the core level, we use symbol keys, only (e.g. :"contract.default").

The default implementation of the context object can be set by overriding Context.implementation. For example, if you want the old behavior back.


class Trailblazer::Context
  def self.implementation
    Context # old behavior.
  end
end

Note that the override might be deprecated in favor of a dependency injection.

Nested

The Nested macro allows to, well, nest activities or operations, providing a neat way to encapsulate and reuse logic.

In 2.1, the [Subprocess macro] is the standard way for nesting. The Nested macro should only be used when you use the dynamic version where the nested operation is computed at runtime using :builder.

An exception will warn you about the inappropriate usage.


[Trailblazer] Using the `Nested()` macro with operations and activities is deprecated. Replace `Nested(Create)` with `Subprocess(Create)`.

Both the :input and :output options that used to go with Nested(Present, :input: ...) are now a generic option in Trailblazer. Move them behind the macro parenthesis.


# 2.0
Nested(Present, input: :my_input, output: :my_output)

# 2.1
Nested(Present), input: :my_input, output: :my_output

An exception will stop compilation if you fail to obey.


ArgumentError: unknown keyword: input

Macro API

Macros are functions that add a task along with specific options to the activity. In TRB 2.0, those (historically camel-cased) functions returned an array with two elements.


module MyMacro
  def self.NormalizeParams(name: :myparams, merge_hash: {})
    task = ->((ctx, flow_options), _) do
      ctx[name] = ctx[:params].merge(merge_hash)

      return Trailblazer::Activity::Right, [ctx, flow_options]
    end

    [task, name: name] # old API
  end
end

In 2.1, a hash is returned. Note that :name is :id now.


module MyMacro
  def self.NormalizeParams(name: :myparams, merge_hash: {})
    task = ->((ctx, flow_options), _) do
      ctx[name] = ctx[:params].merge(merge_hash)

      return Trailblazer::Activity::Right, [ctx, flow_options]
    end

    # new API
    {
      task: task,
      id:   name
    }
  end
end

This allows for a much richer macro experience where you might add additional steps via a macro, use DSL options such as :before and :after and add taskWrap extensions. [macro API]

Contract DSL

It was possible to define contracts on the operation level using a DSL.


class Create < Trailblazer::Operation
  contract do
    property :id
  end

  step Contract::Build()
  step Contract::Validate()
end

Since the usability doesn’t outweigh the complexity needed to implement such DSL, we decided to remove that functionality for now.

Instead, use an explicit inline class and the :constant option.


class Create < Trailblazer::Operation
  class Form < Reform::Form
    property :id
  end

  step Contract::Build(constant: Form)
  step Contract::Validate()
end

Context

Hello hello early birds

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque ac ligula convallis, mollis velit eu, porta odio. Proin nibh ipsum, bibendum eu auctor volutpat, consectetur vitae erat. Duis condimentum dapibus hendrerit.