# 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
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
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
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
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
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
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
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
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
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
← Interactors REST →