SayWhen

SayWhen is a job scheduling library for use in any project, but with a few extra hooks for rails projects. It was roughly inspired by the Quartz scheduler.

You add it to a ruby program (optionally configure it) and then schedule jobs using a few different strategies, with cron-like expressions the most powerful.

When scheduling, you specify a trigger which controls when execution will occur, such as a cron trigger, or an execute only once trigger, and a job, which is the actual work to perform.

The cron triggers are based on the extended cron capabilities.

Jobs can be stored different ways, either in memory (e.g. loaded on start from a ruby file), or saved to a database.

The scheduler can execute the jobs in different ways, either by loading and running them itself synchronously, or by delegating the processing to ActiveJob.

SayWhen can be run either in its own process, or can run as a supervised actor in a Celluloid process (e.g. sidekiq or shoryuken).

Installation

Add this line to your application's Gemfile:

gem 'say_when'

And then execute:

$ bundle

Or install it yourself as:

$ gem install say_when

Configuration

To use, first configure how jobs are stored and processed. Be default, they are in memory, and processed synchronously, but that can be configured to behave differently.

The currently available storage options are:

  • Memory (default) - usually jobs are initialized in code on load
  • ActiveRecord - stores the scheduled jobs and can log execution information to database tables

The processor options are:

  • Simple - the scheduler process executes the job itself synchronously
  • ActiveJob - delegates the work to an ActiveJob async call
  • Test - stubs out processing, useful for testing only

You also have some options for running SayWhen:

  • SimplePoller - just a simple looping process, can be started with rake
  • CelluloidPoller - defines a Celluloid Actor appropriate for adding to a running Celluloid process, such as from Shoryuken

Finally, there are options for triggers that determine when jobs run:

  • Cron expression
  • Once
  • Instance
# config/intializers/say_when.rb

require 'say_when'

# you can specify a the logger
SayWhen.logger = Rails.logger

# configure the scheduler for how to store and process scheduled jobs
# it will default to a :memory strategy and :simple processor
SayWhen.configure do |options|
  options[:storage_strategy]   = :active_record
  options[:processor_strategy] = :simple
end

Usage

Basics

There is a very verbose way to create a scheduled job, setting the options for the trigger and job explicitly, and then some helper methods that make this easier for common cases.

Here is an example of the verbose way of scheduling a job, which is a good place to start:


  job = SayWhen::Scheduler.schedule(
    trigger_strategy: 'once',
    trigger_options: { at: 10.second.since },
    job_class: SomeTask,
    job_method: 'execute',
    data: { id: 123 }
  )

There are also convenience methods on the Scheduler:


ActiveRecord integration

Besides storing jobs in ActiveRecord, you can also associate jobs with other models.

There is an acts_as_scheduled method you can call in an ActiveRecord class for this purpose. It both makes it easier to schedule a job, and to see manage the list of related jobs.

For example, you might create a job to send a reminder a week after a user is created, and relate this new job to that user. By associating it with the ActiveRecord object, you can more easily manage this reminder, such as canceling it if they close their account.

When using ActiveRecord integration in Rails, there is a generator for the migration to create the tables for saving scheduled jobs:

bundle exec rails generate say_when:migration

The resulting migration assumes the scheduled jobs will use a integer based id column, please update the default migration if this is not the case in your system:

# change this to string or other type as needed
t.integer   :scheduled_id

Pollers

The SimplePoller does what you would expect; when you run it, it starts up a loop of checking for jobs to run, sleeping, and then checking again.

It can be executed from the rake task:

bundle exec rake say_when:start

But that isn't doing much except loading the environment then this:

require 'say_when'
require 'say_when/poller/simple_poller'

SayWhen::Poller::SimplePoller.start

For my own purposes, I use things like daemontools and god to daemonize, so this has been enough for me, but it would not be hard to write a command line script for it.

Most of the time I am also running a job processor, either shoryuken or sidekiq, and I would prefer to piggyback on that same process instead of starting up another. Since both of those use Celluloid (or did, Sidekiq no longer does), I also created a Celluloid actor class that can be added to the celluloid based job process via hooks in their startup.

For Shoryuken, add this to your initializer (probably config/initializers/shoryuken.rb):

require 'say_when/poller/celluloid_poller'

Shoryuken.on_start do
  # check for new jobs to run every 5 seconds
  SayWhen::Poller::CelluloidPoller.supervise_as :say_when, 5
end

For Sidekiq, there is a slightly different syntax, but basically the same idea (via https://github.com/mperham/sidekiq/wiki/Deployment#events):

require 'say_when/poller/celluloid_poller'

Sidekiq.configure_server do |config|
  config.on(:startup) do
    SayWhen::Poller::CelluloidPoller.supervise_as :say_when, 5
  end
end

Contributing

  1. Fork it ( https://github.com/[my-github-username]/say_when/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request