Expirable
Expirable is a simple Rails mixin to handle deadlines in workflows. It builds on Delayed::Job and Clockwork to provide a painless approach to handling time-based events.
Installation
To install the Expirable and its dependencies, add expirable
and an
appropriate Delayed::Jobs backend to your Gemfile:
gem 'delayed_job_active_record'
gem 'expirable'
Next, set up Delayed::Job if you are not already using it in your project:
rails generate delayed_job:active_record
rake db:migrate
Finally, set up Expirable itself by running rails g expirable:setup
.
This will create a Clockwork configuration file called lib/clock.rb.
By default, it is configured to check deadlines at one minute past every hour.
See the Clockwork manual
for more information if you need to change this configuration.
Running Expirable
To run Expirable, you will need to run a Clockwork worker and a Delayed::Job worker.
You can run the Clockwork worker by executing bundle exec clockwork lib/clock.rb
from the root of the Rails project, and Delayed::Job worker with ./bin/delayed_job run
.
You can also use Foreman
to manage these processes. For example, if you run your Rails application on Unicorn, then
you can create a Procfile
with the following content and run your application with foreman start
:
cron: bundle exec clockwork lib/clock.rb
web: bundle exec unicorn_rails
worker: ./bin/delayed_job run
Handling Deadlines
To be expirable, a class must do the following three things:
- include
Expirable
; - define a
deadline_expired
instance method that will be invoked when the model's deadline expires; and - define a class method named
newly_expired
that returns a collection of model instances that have expired, but haven't yet received adeadline_expired
message.
As an example, the following model uses AASM and Expirable to transition tasks to a failed state when their deadline is passed:
class ExampleTask < ActiveRecord::Base
include AASM
include Expirable
scope :newly_expired,
-> { in_progress.where('deadline < ?', DateTime.now) }
aasm do
# ... state and event definitions skipped ...
event :deadline_expired do
transitions from: :in_progress, to: :missed
end
end
end