Guide

Recurring tasks

Run jobs on a cron schedule — a YAML file (SolidQueue-compatible) or the config hash.

Install the tables#

Recurring tasks need two tables — one for the task definitions, one for the execution ledger that keeps a schedule from firing twice. Add them:

rails generate pgbus:add_recurring                  # add the migration + config/recurring.yml
rails generate pgbus:add_recurring --database=pgbus # for a separate database
rails db:migrate

The generator also writes a starter config/recurring.yml. A scheduler process (part of the supervisor) reads your tasks, syncs them to the database, and enqueues each on its schedule.

Declare tasks in recurring.yml#

SolidQueue-compatible; environment-scoped or flat.

The generated file is compatible with SolidQueue's recurring.yml, so a migration is mostly a copy. Each task is a key with a schedule and either a class (an ActiveJob class) or a command (Ruby to run):

config/recurring.yml
production:
  periodic_cleanup:
    class: CleanupJob
    queue: maintenance
    args: [1000, { batch_size: 500 }]
    schedule: every hour

  daily_report:
    class: DailyReportJob
    schedule: "0 8 * * mon-fri"
    description: Generate the daily business report
The file is ERB-evaluated and can be environment-scoped (a production: top-level key) or flat. Point at it with config.recurring_tasks_file if it lives elsewhere.

Or declare them in the initializer#

If you'd rather keep everything in Ruby, set config.recurring_tasks to a hash of the same shape:

config/initializers/pgbus.rb
Pgbus.configure do |c|
  c.recurring_tasks = {
    periodic_cleanup: {
      class: "CleanupJob",
      schedule: "every hour",
      queue: "maintenance",
      args: [1000, { batch_size: 500 }]
    }
  }
end

Schedule syntax#

Cron expressions or natural language, parsed by fugit.

Schedules are parsed by fugit, so both cron expressions and natural-language phrases work:

ScheduleMeans
"0 2 * * *"Every day at 2:00 AM
"*/5 * * * *"Every 5 minutes
"every hour"Every hour at :00
"every day at 2am"Daily at 2:00 AM
"@daily"Daily at midnight
"0 9 * * mon-fri"Weekdays at 9:00 AM

class: vs command:#

A task runs either a job class or an inline command — one is required. Use class: to enqueue an ActiveJob (the usual case); use command: for a one-off snippet with no job class:

config/recurring.yml
cleanup_old_records:
  command: "OldRecord.where('created_at < ?', 30.days.ago).delete_all"
  schedule: every day at 3am
A command: string is evaluated as Ruby in the worker — keep it to trusted, version-controlled snippets. For anything non-trivial, prefer a class: job you can test.