skills/rails-jobs-patterns/SKILL.md
Analyzes and recommends ActiveJob background processing patterns including idempotency, retry strategies, batch processing, scheduled jobs, and queue management. Use when creating background jobs, configuring Sidekiq, handling async workflows, or optimizing job performance. NOT for synchronous controller actions, model callbacks, service object design, or real-time Turbo Streams.
npx skillsauth add ag0os/rails-dev-plugin rails-jobs-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Analyze and recommend patterns for reliable, efficient background jobs in Rails applications.
Follow standard Rails conventions for retry_on, discard_on, queue_as, and basic ActiveJob structure. Focus on the opinionated patterns below.
| Pattern | Use When |
|---------|----------|
| _later/_now convention | Every async operation — name both versions |
| Shallow job + model logic | All new jobs — jobs are wrappers only |
| Double-check locking | Idempotency with concurrent workers |
| Self-splitting batch | Datasets too large for one job |
| limits_concurrency | Solid Queue race condition prevention |
| perform_all_later | Bulk enqueueing (Rails 7.1+) |
rails-stack-profiles) — models and concerns for native, service objects for extracted. Jobs handle only queuing and retry infrastructure_later/_now convention: Define thing_later (queues job) and thing (does work) on the model. The job just calls the model methodwith_lock THEN guard again — prevents races between concurrent workers_later/_now Pattern (37signals)# Model defines both versions
class Webhook::Delivery < ApplicationRecord
after_create_commit :deliver_later
def deliver_later
Webhook::DeliveryJob.perform_later(self)
end
def deliver
in_progress!
self.response = perform_request
completed!
rescue => e
failed!(e.message)
end
end
# Job is a shallow wrapper — no business logic
class Webhook::DeliveryJob < ApplicationJob
queue_as :webhooks
def perform(delivery)
delivery.deliver
end
end
The critical pattern — guard clause alone has a race window. Always combine with with_lock:
def perform(import_id)
import = Import.find(import_id)
return if import.completed? # Fast path: skip lock if done
import.with_lock do
return if import.completed? # Safe path: re-check under lock
process_import(import)
import.update!(status: "completed")
end
end
For datasets too large for one job — the job re-enqueues itself for the next chunk:
class LargeImportJob < ApplicationJob
BATCH_SIZE = 1_000
def perform(dataset_id, offset = 0)
records = Dataset.find(dataset_id).records.offset(offset).limit(BATCH_SIZE)
return if records.empty?
process_batch(records)
self.class.perform_later(dataset_id, offset + BATCH_SIZE)
end
end
limits_concurrencyclass Storage::MaterializeJob < ApplicationJob
queue_as :backend
limits_concurrency to: 1, key: ->(owner) { owner }
def perform(owner)
owner.materialize_storage
end
end
sidekiq-unique-jobsclass SyncUserJob < ApplicationJob
sidekiq_options lock: :until_executed,
lock_args_method: ->(args) { [args.first] }
def perform(user_id)
UserSyncService.new(user_id).sync!
end
end
Lock types: :until_executing (unique in queue), :until_executed (through completion), :until_and_while_executing (most restrictive).
class Notification::Bundle
def self.deliver_all_later
due.find_in_batches do |batch|
jobs = batch.map { |bundle| DeliverJob.new(bundle) }
ActiveJob.perform_all_later(jobs) # Single DB operation
end
end
end
For apps using CurrentAttributes with multi-tenancy — automatically serialize account context across job execution. See patterns.md for the FizzyActiveJobExtensions pattern that captures Current.account on enqueue and restores it on perform.
Skip this pattern for single-tenant apps.
| Anti-Pattern | Fix |
|-------------|-----|
| Business logic inside job | Delegate to its Axis A home (see Principle 1) |
| Guard clause without with_lock | Use double-check locking pattern above |
| find_in_batches all in one job | Self-splitting batch or perform_all_later |
| No queue segmentation | Use priority queues (critical/default/low/mailers) |
| Check | Command | Implication |
|-------|---------|-------------|
| Job adapter | grep "queue_adapter" config/ | Solid Queue vs Sidekiq patterns |
| Multi-tenancy | grep "Current.account" app/ | Context serialization needed |
| Rails version | grep "rails " Gemfile.lock | perform_all_later available 7.1+ |
| Recurring jobs | Check config/recurring.yml or sidekiq_schedule.yml | Respect existing scheduler |
When analyzing or creating jobs, provide:
app/jobs/ with retry/discard configurationActiveJob::TestHelperdevelopment
WHAT: Language-agnostic corrective guidance for the refactoring phase. WHEN: Agent is restructuring code, fixing code smells, reducing complexity, or improving maintainability. NOT FOR: Writing new features, debugging runtime errors, performance tuning, or object design decisions.
tools
Analyzes Rails view templates, partials, layouts, helpers, and form patterns for best practices. Use when reviewing ERB templates, improving view performance with fragment caching, fixing form helpers, organizing partials, adding accessibility attributes, or evaluating collection rendering. NOT for Stimulus/Turbo logic (use hotwire-patterns), controller concerns, or API-only responses.
testing
Analyzes Rails test suites and recommends testing best practices for RSpec and Minitest. Use when writing new tests, reviewing test coverage, fixing flaky tests, improving test performance, choosing between test types (unit, integration, system, request), or setting up factories and fixtures. NOT for production monitoring, deployment verification, or load/stress testing infrastructure.
development
Detects a Rails project's architecture axes — logic placement (native vs extracted) and delivery (html vs api) — so other skills load profile-appropriate guidance without inline conditionals. Use when planning architecture or when a recommendation depends on where business logic lives or whether the app renders HTML or serves JSON. NOT for test framework, job backend, cache store, or auth library choices — those are orthogonal facts detected by project-conventions.