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:

```shell
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):

```yaml
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
```

> **Note:** 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:

```ruby
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](https://github.com/floraison/fugit), so both cron expressions and natural-language phrases work:

| Schedule | Means |
| --- | --- |
| `"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:

```yaml
cleanup_old_records:
  command: "OldRecord.where('created_at < ?', 30.days.ago).delete_all"
  schedule: every day at 3am
```

> **Warning:** 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.