# Trailblazer Hanami: 1.x

Gemfile

gem 'trailblazer-operation'
1

# Simple Operation

module Op
  module Post
    class Create < Trailblazer::Operation
      include ::OperationHelpers

      pass :prepare_params
      step :validate, Output(:failure) => End(:invalid_params)
      step :create

      def prepare_params(ctx, params:, **)
        params[:email].downcase! if params[:text].present?
      end

      def validate(ctx, params:, **)
        sch = Dry::Validation.Form do
          required(:text).filled(min_size?: 255)
        end
        ctx[:validation_result] = sch.call(params)
        ctx[:params] = ctx[:validation_result].to_h
        handle_validation_errors(ctx)
      end

      def create(ctx, params:, **)
        ctx[:model] = PostRepository.new.create(params)
      end
    end
  end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

call it via

res = Op::Post::Create.call{text: 'Abc ... more text'}
res.success?
res[:model]  # return exemplar of created Post from Repository
1
2
3

# Handle errors

lib/helpers/operation_helpers.rb


 























module OperationHelpers
  def add_error(ctx, attr, error)
    ctx[:errors] = ctx[:errors].nil? ? {} : ctx[:errors]
    ctx[:errors][attr] ||= []
    ctx[:errors][attr] << error
    ctx[:errors][attr].flatten!

    # Hacky return false to route operation to :failure track
    # if step is ending on add_error
    false
  end

  def handle_validation_errors(ctx)
    return true if ctx[:validation_result].success?

    errors = ctx[:validation_result].errors
    errors.each do |attr, messages|
      messages.each do |attr, messages|
        add_error(ctx, attr, messages.join(', '))
      end
    end
    false
  end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

lib/web.rb

require_relative './helpers/operation_helpers'
#...
1
2

Now inside Operation you can do


 





 






class OpWithError < Trailblazer::Operation
  include ::OperationHelpers

  step :any_step

  def any_step(ctx, **)
    if ctx[:smth_wrong] == true
      add_error(ctx, :some_attr_anme, "Specific error message for this error")
    else
      true
    end
  end
end
1
2
3
4
5
6
7
8
9
10
11
12
13

to get errors after call


 

res = OpWithError.call(smth_wrong: true)
errors = res[:errors]
1
2

# Custom Macroses

Let's assume you want to reduce manual decorator calls in Operations


 




 

 




class OpWithError < Trailblazer::Operation
  include ::OperationHelpers

  # from this (and the need to duolicate method)
  step :decorate_model
  # to this
  step Macro::Decorate(:model, with: 'UserDecorator')

  def decorate_model(ctx, **)
    ctx[:model] = UserDecorator.new(ctx[:model])
  end
end
1
2
3
4
5
6
7
8
9
10
11
12

create lib/macros/decorate.rb and require it in lib/web.rb

lib/macros/decorate.rb





 































 
 


module Macro
  include Trailblazer

  def self.Decorate(entity_context_key, with: nil)
    step = lambda do |ctx, **|
      return if ctx[entity_context_key].blank?

      if with.blank?
        begin
          ctx[entity_context_key] = ctx[entity_context_key].decorate
        rescue NoMethodError => e
          error_msg = "Unable to decorate!\r\nThere should be `decorate` method on decorable or Decorator class passed"
          Hanami.logger.error(error_msg)
          ctx[:errors] ||= {}
          ctx[:errors][:decoration] = error_msg
          return false
        end
      else
        begin
          if with.is_a?(String)
            with = Object.const_get(with)
          else
            with = Object.const_get("::#{with.to_s}")
          end

          ctx[entity_context_key] = with.new(ctx[entity_context_key])
        rescue NameError => e
          error_msg = "Unable to decorate!\r\nPassed Decorator does not exist."
          Hanami.logger.error(error_msg)
          ctx[:errors] ||= {}
          ctx[:errors][:decoration] = error_msg
          return false
        end
      end
    end

    task = Activity::TaskBuilder::Binary(step)
    { task: task, id: "model.decorate" }
  end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# Custom Macros with Track change

You can do even more DRY macroses that automatically end up on specified Track in case of error

Explicit Track switch like this

step :check_permission, Output(:failure) => End(:fobidden)
1

could be omited with

step Macro::CheckPermission
1














 




module Macro
  include Trailblazer

  def self.CheckPermission()
    step = lambda do |ctx, **|
      # decide to return TRUE or FALSE
      # depending on data in CTX
    end

    task = Activity::TaskBuilder::Binary(step)

    {
      task: task,
      id: "check_permission",
      Trailblazer::Activity.Output(Trailblazer::Activity::Left, :failure) => Trailblazer::Activity.End(:forbidden)
    }
  end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

:forbidden here sould be changed to anything you want