Steps

Steps

Steps are the core components of a service, each representing a unit of work executed in sequence when the service is called.

TL;DR

  • Define steps using the step keyword within the service class

  • Use if and unless options for conditional steps

  • Inherit steps from parent classes

  • Inject steps into the execution flow with before and after options

  • Ensure cleanup steps run with the always: true option (unless done! was called)

  • Use a run method as a simple alternative for single-step services

class GeneralParserService < ApplicationService
  step :create_browser, unless: :browser
  step :parse_content
  step :quit_browser, always: true
end

class ParsePage < GeneralParserService
  step :parse_additional_content, after: :parse_content
end

Define Steps

Steps are declared using the step keyword in your service class.

Conditional Steps

Steps can be conditional, executed based on specified conditions using the if or unless keywords.

This feature works well with argument predicates.

Using Procs for Conditions

You can also use Procs (lambdas) for inline conditions:

Using Procs can make simple conditions more readable, but for complex logic, prefer extracting to a method.

Inheritance

Steps are inherited from parent classes, making it easy to build upon existing services.

Injecting Steps into Execution Flow

Steps can be injected at specific points in the execution flow using before and after options.

Let's enhance the previous example by adding a step to send a notification after updating the record.

Combine this with if and unless options for more control.

By default, if neither before nor after is specified, the step is added at the end of the execution flow.

Always Running Steps

To ensure certain steps run regardless of previous step outcomes (errors, warnings, failed validations), use the always: true option. This is particularly useful for cleanup tasks, error logging, etc.

Note: if done! was called, the service exits early and always: true steps will not run.

Early Exit with stop!

Use stop! to stop executing remaining steps without adding an error. This is useful when you've completed the service's goal early and don't need to run subsequent steps.

You can check if stop! was called using stopped?:

stop! stops subsequent steps from running, including steps marked with always: true. Code after stop! within the same step method will still execute.

Backward Compatibility: done! and done? are still available as aliases for stop! and stopped?.

Immediate Exit with stop_immediately!

Use stop_immediately! when you need to halt execution immediately, even within the current step. Unlike stop!, code after stop_immediately! in the same step method will NOT execute.

Immediate Failure with fail_immediately!

Use fail_immediately! when you need to halt execution immediately AND rollback any database transactions. Unlike stop_immediately!, this method adds an error and causes transaction rollback.

fail_immediately! raises an internal exception to halt execution. Steps marked with always: true will still run when fail_immediately! is called, allowing for cleanup operations.

Comparison Table

Method
Adds Error
Stops Execution
Transaction Rollback

stop!

No

After current step

No

stop_immediately!

No

Immediately

No

fail!(msg)

Yes (:base)

After current step*

No

fail_immediately!(msg)

Yes (:base)

Immediately

Yes

*By default, adding an error stops subsequent steps from running due to break_on_add configuration.

Removing Inherited Steps

When inheriting from a parent service, you can remove steps using remove_step:

Using run Method as a Simple Alternative

For simple services that don't need multiple steps, you can define a run method instead of using the step DSL. If no steps are defined, Operandi will automatically use the run method as a single step.

This is equivalent to:

Inheritance with run Method

The run method works with inheritance. If a parent service defines a run method, child services will inherit it:

If a service has no steps defined and no run method (including from parent classes), a Operandi::NoStepsError will be raised when the service is executed.

What's Next?

Next step is to learn about outputs. Outputs are the results of a service, returned upon completion of service execution.

Next: Outputs

Last updated