Enqueue jobs on state machine transitions and change state according to job result.
Add this line to your application's Gemfile:
gem 'state_machine_job'
Include the StateMachineJob
mixin in your job and provide a
perform_with_result
method instead of the normal perform
method:
class SomeJob < ApplicationJob
include StateMachineJob
def perform_with_result(record, payload)
# do something
:ok
end
end
The record
parameter is a reference to the object the state machine
will be defined on.
Now you can wire up the job in a state machine using the
StateMachineJob::Macro
:
state_machine :initial => 'idle' do
extend StateMachineJob::Macro
state 'idle'
state 'running'
state 'done'
state 'failed'
event :run do
transition 'idle' => "running"
end
job SomeJob do
on_enter 'running'
result :ok => 'done'
result :error => 'failed'
end
end
When the state
attribute changes to 'running'
(either by the run
event or by manually updateing the attribute), SomeJob
will
automatically be enqueued. If perform_with_result
returns :ok
, the
state machine transitions to the 'done'
state. You can specify as
many results as you want. Note that any exception raised by
perform_with_result
leads to a state machine transition as if the
result had been :error
. The exception is not rescued, though.
You can specify further options to pass to the perform_with_result
method using the payload
method:
job SomeJob do
on_enter 'running'
payload do |record|
{:some_attribute => record.some_attribute}
end
result :ok => 'done'
result :error => 'failed'
end
perform_with_result
is now called with the given hash of options as
the second parameter.
One job result can lead to different states based on a conditional. When the job finishes with the given result, the state machine transitions to the first state whose conditional evaluates to true.
job SomeJob do
on_enter 'running'
result :ok, :state => 'special', :if => lambda { |record| record.some_condition? }
result :ok, :state => 'other', :if => :other_condition?
result :ok, :state => 'done'
result :error => 'failed'
end
A conditional can either be a lambda optionally accepting the record as parameter or a symbol specifying a method to call on the record.
You can tell the state machine to retry a job based on its result:
job SomeJob do
on_enter 'running'
result :ok => 'done'
result :pending, :retry_after => 2.minutes
result :error => 'failed'
end
When perform_with_result
returns the result :pending
, the state
machine will remain in the runnning
state and enqueue a delayed
job. This feature uses the Active Job set(wait: n)
functionality.
You can tell the state machine to retry a job if a transition to a certain state occures while a job is running:
event :run do
transition 'idle' => 'running'
transition 'running' => 'rerun_requested'
end
job SomeJob do
on_enter 'running'
result :ok, :state => 'done', :retry_if_state => 'rerun_requested'
result :error => 'failed'
end
If the run
event is invoked while the job is already running, you
can transition to a state signaling that the job will need to run
again once it has finished. In example, passing the :retry_if_state
option causes the state machine to transition back to the running
state once the job finishes with result :ok
.