Skip to content

Step logical types

Artem Shevchenko edited this page Nov 15, 2025 · 10 revisions

Logic flow is based on different step logic types regardless of invocable type. All logic types share the same set of reserved logic options. And some of the logic types have their own set of technical options (e. g. Switch).

Basic features

Each of logic types has those options:

  • fast
  • on_success
  • on_failure
  • name

fast

This option defines when flow execution should be finished immediately and return execution results. By default this options is unset but you can set it for any step to one of those values: true, :success, :failure

fast: true

If option is set to true regardless of step result flow execution will be finished. Result of the step will be used to determine service is succeeded or failed

Example:

class Service < Yaso::Service
  step :one
  step :two, fast: true
  # Flow execution will be finished on step "two" and never reaches steps "three" and "four"
  step :three
  failure :four
end

fast: :success

If option is set to :success flow execution will be finished only if step succeeds so service will succeed too.

Example:

class Service < Yaso::Service
  step :one
  step :two, fast: :success
  # Flow execution will be finished on step "two" if "two" succeeds
  # otherwise execution will be directed to failure "four"
  # so step "three" is unreachable in this case
  step :three
  failure :four
end

fast: :failure

If option is set to :failure flow execution will be finished only if step fails so service will fail too.

Example:

class Service < Yaso::Service
  step :one
  step :two, fast: :failure
  # Flow execution will be finished on step "two" if "two" fails
  # otherwise execution will be directed to step "three"
  # but note that if step "one" fails execution will be directed to failure "four"
  step :three
  failure :four
end

on_success

This option defines to which step flow execution should be directed if step succeeds. By default this options is unset and next step will be set by the flow automatically but you can set it for any step to any step name that your service have. Note that you can't direct execution to Wrap logic type inner step outside of Wrap instance and vice versa, more information in the Wrap section. If you want to direct flow execution to the step with callable or yaso invocable type then you could use logical name option

Example:

class Service < Yaso::Service
  step :one
  pass :two, on_success: :four
  # If step "two" succeeds flow execution will jump to step "four" skipping step "three"
  # but if step "two" fails flow execution will go by the default way (steps "three" -> "four")
  step :three
  step :four
end

on_failure

This option defines to which step flow execution should be directed if step fails. By default this options is unset and next failure step will be set by the flow automatically but you can set it for any step to any step name that your service have. Note that you can't direct execution to Wrap logic type inner step outside of Wrap instance and vice versa, more information in the Wrap section. If you want to direct flow execution to the step with callable or yaso invocable type then you could use logical name option

Example:

class Service < Yaso::Service
  step :one
  step :two, on_failure: :four
  # If step "two" fails flow execution will jump to step "four" skipping step "three"
  # but if step "two" succeeds flow execution will go by the default way (steps "three" -> "four")
  step :three
  step :four
end

name

This option defines step name that could be referenced in other steps on_success and on_failure options. Note that this option could be applied only to callable and yaso invocable step types because for inline and method invocable types name is already defined in the step definition and custom name will be simply ignored.

class Service < Yaso::Service
  step :one
  step :two, on_failure: :four
  # If step "two" fails flow execution will be directed to the step with name "four"
  step :three
  step CallableStep, name: :four
  # so it will be directed to this step
end

Step

Step is a basic step logical type that invokes business logic and resolves step result by it. If invocable succeeds flow execution will be directed to next step and flow execution will be resolved as successful otherwise to the next failure and flow execution will be resolved as failed. Note that custom on_success and on_failure steps could point to any step logic type so if step fails and directs flow execution to the logic type Step context status will be reset to success.

Example:

class Service < Yaso::Service
  step :one
  step :two, on_failure: :four
  # If step "two" fails flow execution will be directed to the step with name "four"
  # bypassing step "three" and flow execution will be successful
  step :three
  step :four
end

Failure

Failure is a logical type that invokes business logic and sets flow execution status to failure. Regardless of invokable result flow execution will be directed to the next failure step. Note that custom on_success and on_failure steps could point to any step logic type so if step directs flow execution to the logic type Failure context status will be reset to failure regardless of previous status.

Examples:

class Service < Yaso::Service
  step :one
  failure :two
  # If step "one" fails flow execution will be directed to the failure with name "two"
  # and will skip steps "three" and "four"
  step :three
  step :four
end

When on_success points to failure

class Service < Yaso::Service
  pass :one, on_success: :two
  failure :two
  # If step "one" succeeds flow execution will be directed to the failure with name "two",
  # fail and skip steps "three" and "four" otherwise will skip failure "two" and go by default flow
  step :three
  step :four
end

Pass

Pass is a logical type that invokes business logic and sets flow execution status to success. Regardless of invokable result flow execution will be directed to the next step. Note that custom on_success and on_failure steps could point to any step logic type so if step directs flow execution to the logic type Pass context status will be reset to success regardless of previous status.

class Service < Yaso::Service
  pass :one
  step :two
  # If step "one" fails flow execution will be directed to the step "two"
  # regardless of invocable result
  failure :three
  step :four
end

Wrap

Wrap is a logical type that invokes nested business logic by wrapping it into a block. Block returns true if steps succeed and false otherwise. Wrap result is resolved in the same way as for Step type. Consider Wrap as a nested service, steps inside the wrap doesn't know about steps outside of it and vice versa. Wrap could be either a method or a callable.

Examples:

Wrap can be a method:

class Service < Yaso::Service
  wrap :one do
    step :two
  end

  def one(ctx, **, &block)
    # your code goes here...
    block.call
    # and here...
  end

  # or

  def one(ctx, **)
    # your code goes here...
    yield
    # and here...
  end
end

Or a callable:

class WrapHandler
  def self.call(ctx, **options, &block)
    # your code goes here...
    block.call
    # and here...
  end
end

class Service < Yaso::Service
  wrap WrapHandler do
    step :two
  end
end

Switch

Switch is a logical type that invokes one of the steps based on the provided cases. Consider it as a case/when statement or a Strategy pattern or any other smart name you identify it. Switch should return an invocable (callable class, yaso service class or method name as a symbol). Switch could be either a method, a callable or hidden. Note that error will be raised if switch returns non-invocable value.

Examples:

Switch as a hidden:

class Service < Yaso::Service
  # It means that switch will do the job for you
  # if you don't need additional logic and
  # simply want to retrieve invocable from the cases
  switch :one, key: :key_from_ctx, cases: {foo: :bar, bar: Foo}

  def bar(ctx, **)
    # your code goes here...
  end
end

Switch as a method:

class Service < Yaso::Service
  CASES = {foo_1: :foo_1, foo_2: FooTwo, bar_1: BarOne, bar_2: YasoBar}
  switch :one

  def one(ctx, **)
    # The use-case is when you need additional logic
    # to resolve which case to invoke
    key = "#{ctx[:key_from_ctx]}-#{rand(1..2}" # just an example
    CASES[key]
  end

  def foo_1(ctx, **)
    # your code goes here...
  end
end

Switch as a callable:

class SwitchHandler
  CASES = {foo_1: :foo_1, foo_2: FooTwo, bar_1: BarOne, bar_2: YasoBar}

  def self.call(ctx, **options)
    # The use-case is when you need additional logic
    # to resolve which case to invoke
    key = "#{ctx[:key_from_ctx]}-#{rand(1..2}" # just an example
    CASES[key]
  end
end

class Service < Yaso::Service
  switch SwitchHandler

  def foo_1(ctx, **)
    # your code goes here...
  end
end

Clone this wiki locally