Make your services running step-by-step with error handling, logging and easy testing.
add to your Gemfile:
gem "stepped", github: "datacrafts-io/stepped"
include Stepped
into your serviceclass ClassName
include Stepped
...
end
param
and option
keywords to define initial arguments: class ClassName
...
param :argument_name, # reader name
->(value) { value.to_s }, # optional proc for type coercing
default: -> { "some string" } # optional proc with default value
...
end
param
is used to define arguments, option
is for keyword arguments
pass: false
to cancel passing result of current step to nextcache: true
to save result of current step and access it in another steps later via step_result(:step_name)
from: :step_name
to receive arguments from :step_name
instead of previous step (must be combined with cache: true
for from
step)on_failure: [handler]
, [handler]
is Proc
or :method_name
receiving 2 arguments: :step_name
and error
instance class ClassName
...
step :step_one, cache: true do
puts "I'm the step one"
"Result of Step One"
end
step :step_two, on_failure: ->(_, err) { ... } do |result_of_prev_step|
puts result_of_prev_step # => Result of Step One
end
step :step_three, from: :step_one do |result_of_step_one|
puts result_of_prev_step # => Result of Step One
end
...
end
on_failure
options (default: on_failure stop: false, reraise: true
):First optional argument can be :method_name
or Proc
:
class ClassName
...
on_failure :error_handler, stop: true, # stop process on error
reraise: true # reraise error after error handling
def error_handler(step_name, error)
Notifier.call(step_name, error.message)
end
...
end
class ClassName
...
logger on_start: true, # => [Stepped] Started ClassName with arguments: (arguments below)
before_step: true, # => [Stepped] Step [step_name] received: (arguments below)
after_step: true, # => [Stepped] Step [step_name] passed: (arguments below)
on_end: true, # => [Stepped] ClassName finished and returned: (arguments below)
method: Rails.logger.method(:info) # method, proc, service, etc which has method :call with one arg
...
end
Wrap all steps in block provided by passed arg
class ClassName
...
wrap ActiveRecord::Base.method(:transaction) # pass something which has method :call and can receive block
...
end
# define class
class CreateUser
include Stepped
param :attributes
option :available_points, proc(&:to_i), default: -> { 0 }
on_failure :notify
step :create_user, cache: true do
User.create!(attributes)
end
step :send_email do |user|
UserMailer.send_welcome_email(user)
end
step :assign_points, from: :create_user do |user|
user.add_points(available_points)
user
end
step :notify do |user|
SlackNotifier.user_created(user)
end
private
def notify(step_name, error)
SlackNotifier.user_create_error(error)
end
end
# use
attributes = { name: "John", email: "[email protected]" }
service = CreateUser.new(attributes, available_points: 5)
service.call
# or shortly
CreateUser.call(attributes, available_points: 5)