Errors
Errors are a natural part of every application. This guide explores how to handle errors within Operandi, drawing parallels to ActiveModel errors.
Error Structure
Operandi errors follow a structure similar to ActiveModel errors. Here's a simplified example:
{
email: ["must be a valid email"],
password: ["is too short", "must contain at least one number"]
}Adding Errors
To add an error to your service, use the errors.add method.
class ParsePage < ApplicationService
# Arguments
arg :url, type: String
# ...
# Steps
step :validate
step :parse
# ...
private
def validate
# Multiple errors can be added with the same key
errors.add(:url, "must be a valid URL") unless url.match?(URI::DEFAULT_PARSER.make_regexp)
errors.add(:url, "must be a secure link") unless url.start_with?("https")
end
# ...
endQuick Error with fail!
fail!The fail! method is a shortcut for adding an error to the :base key:
This is equivalent to:
Reading Errors
To check if a service has errors, you can use the #failed? method. You can also use methods like errors.any? to inspect errors.
You can access errors outside the service using the #errors method.
Adding Warnings
Sometimes, you may want to add a warning instead of an error. Warnings are similar to errors but they do not mark the service as failed. By default they also do not stop execution and do not roll back the transaction (both behaviors can be configured globally or per-message).
Copying Errors
From ActiveRecord Models
Use errors.copy_from (or its alias errors.from_record) to copy errors from an ActiveRecord model:
From Another Service
Copy errors from a child service that wasn't run in the same context:
Converting Errors to Hash
Use errors.to_h to get a hash representation of all errors:
Per-Message Options
When adding errors, you can control behavior on a per-message basis:
Control Break Behavior
Control Rollback Behavior
Checking for Errors and Warnings
Operandi provides convenient methods to check error/warning states:
By following these guidelines, you can effectively manage errors and warnings in Operandi, ensuring a smoother and more robust application experience.
Exception Classes
Operandi defines several exception classes for different error scenarios:
Operandi::Error
Base exception class for all Operandi errors
Operandi::ArgTypeError
Raised when an argument or output type validation fails
Operandi::ReservedNameError
Raised when using a reserved name for arguments, outputs, or steps
Operandi::InvalidNameError
Raised when using an invalid name format
Operandi::NoStepsError
Raised when a service has no steps defined and no run method
Operandi::MissingTypeError
Raised when defining an argument or output without a type option when require_arg_type or require_output_type is enabled
Operandi::StopExecution
Control flow exception raised by stop_immediately! to halt execution without rollback
Operandi::FailExecution
Control flow exception raised by fail_immediately! to halt execution and rollback transactions
MissingTypeError
This exception is raised when you define an argument or output without a type option. Since require_arg_type and require_output_type are enabled by default, all arguments and outputs must have a type.
To fix this, add a type option to all arguments and outputs:
If you need to disable type enforcement for legacy services, you can use the config method:
NoStepsError
This exception is raised when you attempt to execute a service that has no steps defined and no run method as a fallback:
To fix this, either define at least one step or implement a run method:
What's next?
Learn about callbacks to add logging, benchmarking, and other cross-cutting concerns to your services.
Last updated