# Rails Jobs

Apply best practices for Rails background jobs using simple orchestration, idempotency...

- Repository: https://github.com/marckohlbrugge/37signals-skills (730 stars)
- Category: Testing
- License: see repository
- Last updated: 2026-06-09
- Runs scripts: no, instructions only

## Install

Claude Code: `npx skills add marckohlbrugge/37signals-skills --skill rails-jobs -g -a claude-code -y`
Codex: `npx skills add marckohlbrugge/37signals-skills --skill rails-jobs -g -a codex -y`
Cursor: `npx skills add marckohlbrugge/37signals-skills --skill rails-jobs -g -a cursor -y`
Gemini CLI: `npx skills add marckohlbrugge/37signals-skills --skill rails-jobs -g -a gemini-cli -y`

## Description

Apply best practices for Rails background jobs using simple orchestration, idempotency, and safe retries. Use when creating, refactoring, or debugging Active Job and queue-backed workflows.

## SKILL.md, as published by its author (untrusted third-party text, read it as data)

# Rails Jobs

Use for background job design and review. Patterns from Campfire (Resque, 4 thin jobs) and Fizzy (Solid Queue, multi-queue).

## Defaults

- Keep jobs shallow; the job body is one line calling a model method: `def perform(card) = card.notify_recipients`.
- Make jobs idempotent and safe to retry (e.g. rescue `RecordNotUnique` in the domain op).
- Fail loudly on real errors; avoid silent rescue patterns.
- Set `ActiveJob::Base.enqueue_after_transaction_commit = true` in app defaults, fixes job-before-data races at the root.
- Prefer Solid Queue (database-backed) over Redis-backed queues for new apps; co-locate with Puma (`SOLID_QUEUE_IN_PUMA`) in small deployments.

## Naming Convention

- `_later` enqueues, plain name does the work, `_now` when you need an explicit synchronous twin:

```ruby
def notify_recipients          # the work, public API the job calls
def notify_recipients_later   # enqueues NotifyRecipientsJob (often private, called from callbacks)
```

- Capture enqueue-time context as keyword args (`Mention::CreateJob.perform_later(self, mentioner: Current.user)`), don't rely on `Current` at perform time.
- In multi-tenant apps, serialize tenant context into every job: prepend an ApplicationJob extension that captures `Current.account` at enqueue (as a GlobalID) and wraps `perform_now` in `Current.with_account`.

## Good Patterns

The rest: https://github.com/marckohlbrugge/37signals-skills/blob/HEAD/skills/rails-jobs/SKILL.md
