skills/igmarin/rails-agent-skills/rails-background-jobs

rails-background-jobs

Installation
SKILL.md

Rails Background Jobs

Use this skill when the task is to add, configure, or review background jobs in a Rails application.

Core principle: Design jobs for idempotency and safe retries. Prefer Active Job's unified API; choose backend based on Rails version and scale.

HARD-GATE

EVERY job MUST have its test written and validated BEFORE implementation.
  1. Write the job spec (idempotency, retry, error handling)
  2. Run the spec — verify it fails because the job does not exist yet
  3. ONLY THEN write the job class

EVERY job that performs a side effect (charge, email, API call) MUST have
an idempotency check BEFORE the side effect.

EVERY perform method should do only three things:
  1. Load the record from the passed ID
  2. Guard for idempotency / permanent no-op conditions
  3. Delegate the side effect or orchestration to a service object

If perform needs more than that, extract a service.

After implementation: run full suite, confirm job appears in queue dashboard,
verify idempotency by enqueueing twice and checking the second run is a no-op.

Quick Reference

Aspect Rule
Arguments Pass IDs, not objects. Load in perform.
Idempotency Check "already done?" before doing work
Retries retry_on for transient, discard_on for permanent errors
Job size Load, guard, delegate. No multi-step orchestration in perform.
Backend (Rails 8) Solid Queue (database-backed, no Redis)
Backend (Rails 7) Sidekiq + Redis for high throughput
Recurring config/recurring.yml (Solid Queue) or cron/sidekiq-cron

Rails 8 vs Rails 7

Aspect Rails 7 and earlier Rails 8
Default No default; set queue_adapter (often Sidekiq) Solid Queue (database-backed)
Dev/test :async or :inline Same
Recurring External (cron, sidekiq-cron) config/recurring.yml
Dashboard Third-party (Sidekiq Web) Mission Control Jobs

See BACKENDS.md for install steps, configuration, and dashboard setup for both Solid Queue and Sidekiq.

Examples

Pass IDs, not objects:

# Bad — object may be stale or deleted by perform time
SomeJob.perform_later(@order)

# Good — reload fresh inside perform
SomeJob.perform_later(@order.id)

Thin job with idempotency and retry:

class SendInvoiceReminderJob < ApplicationJob
  queue_as :default
  retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 5
  discard_on ActiveRecord::RecordNotFound

  def perform(invoice_id)
    invoice = Invoice.find(invoice_id)
    return if invoice.reminder_sent_at?

    InvoiceReminders::Send.call(invoice:)
  end
end

Service owns the side effect and state update:

module InvoiceReminders
  class Send
    def self.call(invoice:)
      InvoiceMailer.overdue(invoice).deliver_now
      invoice.update!(reminder_sent_at: Time.current)
    end
  end
end

Recurring job (Solid Queue):

# config/recurring.yml
production:
  nightly_cleanup:
    class: "NightlyCleanupJob"
    schedule: "0 2 * * *"
  hourly_sync:
    class: "HourlySyncJob"
    schedule: "every 1 hour"
    queue: low

Pitfalls

Problem Correct approach
Passing ActiveRecord objects as arguments Pass IDs — objects may be deleted or stale by perform time
No idempotency check before side effects Jobs run at-least-once; double-charging and double-emailing result
retry_on without attempts limit Infinite retries on persistent errors
Missing discard_on for permanent errors Job retries forever on RecordNotFound
Complex business logic in perform Keep perform thin — delegate to service objects
Using :inline or :async in production No persistence, no retry, no monitoring
Recurring job defined only in code Use recurring.yml or equivalent for visibility and recoverability

Verification

Before calling the job done:

  1. Enqueue or perform the job twice and confirm the second run is a no-op.
  2. Confirm retry_on has an explicit attempts: limit and discard_on covers at least one permanent error.
  3. Confirm recurring jobs live in config/recurring.yml (Rails 8) or the chosen scheduler config.
  4. Confirm perform only loads, guards, and delegates.
  5. If the task asks for an ops artifact, record backend, retry, and idempotency decisions in process_log.md.

Integration

Skill When to chain
rails-migration-safety Solid Queue uses DB tables; add migrations safely
rails-security-review Jobs receive serialized input; validate like any entry point
rspec-best-practices TDD gate: write job spec before implementation; use perform_enqueued_jobs
ruby-service-objects Keep perform thin; call service objects for business logic

Assets

Weekly Installs
13
GitHub Stars
14
First Seen
Mar 29, 2026