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
andunless
options for conditional steps - Inherit steps from parent classes
- Inject steps into the execution flow with
before
andafter
options - Ensure steps always run with the
always: true
option - Retry steps with the
retry
option In Development
Example
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.
class User::Charge < ApplicationService
step :authorize
step :charge
step :send_email_receipt
private
def authorize
# ...
end
def charge
# ...
end
def send_email_receipt
# ...
end
end
Conditional Steps
Steps can be conditional, executed based on specified conditions using the if
or unless
keywords.
class User::Charge < ApplicationService
step :authorize
step :charge
step :send_email_receipt, if: :send_receipt?
# ...
def send_receipt?
rand(2).zero?
end
end
This feature works well with argument predicates.
class User::Charge < ApplicationService
arg :send_receipt, type: :boolean, default: true
step :send_email_receipt, if: :send_receipt?
# ...
end
Inheritance
Steps are inherited from parent classes, making it easy to build upon existing services.
UpdateRecordService
class UpdateRecordService < ApplicationService
arg :record, type: ApplicationRecord
arg :attributes, type: Hash
step :authorize
step :update_record
end
User::Update inherited from UpdateRecordService
class User::Update < UpdateRecordService
# Arguments and steps are inherited from UpdateRecordService
end
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.
User::Update inherited from UpdateRecordService
class User::Update < UpdateRecordService
step :log_action, before: :authorize
step :send_notification, after: :update_record
private
def log_action
# ...
end
def send_notification
# ...
end
end
Combine this with if
and unless
options for more control.
step :send_notification, after: :update_record, if: :send_notification?
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, use the always: true
option. This is particularly useful for cleanup tasks, error logging, etc.
class ParsePage < ApplicationService
arg :url, type: :string
step :create_browser
step :parse_content
step :quit_browser, always: true
private
attr_accessor :browser
def create_browser
self.browser = Watir::Browser.new
end
def parse_content
# ...
end
def quit_browser
browser&.quit
end
end
Retry Failed Steps
In DevelopmentSteps can be retried a specified number of times before giving up, using the retry
option.
class ParsePage < ApplicationService
step :parse_head, retry: true # Retry 3 times by default, no delay between retries
step :parse_body, retry: { times: 3, delay: 1.second }
end
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