rails-background-jobs
Implement background jobs using ActiveJob for asynchronous task processing. Use when tasks are time-consuming, can be processed asynchronously, or should not block user requests (emails, reports, data processing).
$ Installieren
git clone https://github.com/nekorush14/dotfiles /tmp/dotfiles && cp -r /tmp/dotfiles/configs/claude/skills/rails-background-jobs ~/.claude/skills/dotfiles// tip: Run this command in your terminal to install the skill
SKILL.md
name: rails-background-jobs description: Implement background jobs using ActiveJob for asynchronous task processing. Use when tasks are time-consuming, can be processed asynchronously, or should not block user requests (emails, reports, data processing).
Rails Background Jobs Specialist
Specialized in implementing background jobs with ActiveJob and Sidekiq.
When to Use This Skill
- Sending emails asynchronously
- Generating large reports
- Processing uploaded files
- API calls to external services
- Data import/export operations
- Scheduled recurring tasks
Core Principles
- Asynchronous Execution: Don't block user requests
- Idempotency: Jobs can be safely retried
- Error Handling: Graceful failure and retry logic
- Queue Management: Organize jobs by priority
- Monitoring: Track job execution and failures
Implementation Guidelines
Basic Job Structure
# app/jobs/report_generation_job.rb
class ReportGenerationJob < ApplicationJob
queue_as :default
retry_on StandardError, wait: 5.seconds, attempts: 3
def perform(report_id)
report = Report.find(report_id)
# WHY: Use transaction to ensure atomicity
ActiveRecord::Base.transaction do
generator = ReportGenerator.new(report)
data = generator.generate!
report.update!(
status: 'completed',
data: data,
completed_at: Time.current
)
end
# WHY: Notify user when report is ready
ReportMailer.report_ready(report).deliver_now
rescue ActiveRecord::RecordNotFound => e
# WHY: Don't retry if record doesn't exist
Rails.logger.error("Report #{report_id} not found: #{e.message}")
end
end
Enqueuing Jobs
# Immediate execution
ReportGenerationJob.perform_now(report.id)
# Enqueue for background processing
ReportGenerationJob.perform_later(report.id)
# Schedule for later
ReportGenerationJob.set(wait: 1.hour).perform_later(report.id)
# Schedule for specific time
ReportGenerationJob.set(wait_until: Date.tomorrow.noon).perform_later(report.id)
Email Delivery Jobs
# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
def welcome_email(user)
@user = user
mail(to: @user.email, subject: 'Welcome!')
end
end
# Enqueue email job
UserMailer.welcome_email(user).deliver_later
# With delay
UserMailer.welcome_email(user).deliver_later(wait: 1.hour)
# Immediate delivery (not recommended in controllers)
UserMailer.welcome_email(user).deliver_now
Custom Queues
class ReportGenerationJob < ApplicationJob
# WHY: Use low priority queue for resource-intensive reports
queue_as :low_priority
def perform(report_id)
# ...
end
end
class NotificationJob < ApplicationJob
# WHY: High priority for user-facing notifications
queue_as :high_priority
def perform(user_id, message)
# ...
end
end
Retry Configuration
class DataImportJob < ApplicationJob
# Exponential backoff retry
retry_on StandardError, wait: :exponentially_longer, attempts: 5
# Fixed wait time
retry_on NetworkError, wait: 10.seconds, attempts: 3
# Custom retry logic
retry_on CustomError, attempts: 5 do |job, exception|
Rails.logger.warn("Job #{job.job_id} failed: #{exception.message}")
# Custom handling
end
# Don't retry for certain errors
discard_on ActiveRecord::RecordNotFound
def perform(file_path)
# Import logic
end
end
Job Callbacks
class DataProcessingJob < ApplicationJob
before_perform :log_start
after_perform :log_completion
around_perform :measure_time
def perform(data_id)
# Processing logic
end
private
def log_start
Rails.logger.info("Starting job #{job_id}")
end
def log_completion
Rails.logger.info("Completed job #{job_id}")
end
def measure_time
start_time = Time.current
yield
duration = Time.current - start_time
Rails.logger.info("Job took #{duration} seconds")
end
end
Batch Processing Jobs
class UserNotificationJob < ApplicationJob
queue_as :notifications
def perform(user_ids)
User.where(id: user_ids).find_each do |user|
NotificationService.notify(user)
rescue => e
# WHY: Log error but continue processing other users
Rails.logger.error("Failed to notify user #{user.id}: #{e.message}")
end
end
end
# Enqueue in batches
User.active.in_batches(of: 1000) do |users|
UserNotificationJob.perform_later(users.pluck(:id))
end
Scheduled Jobs (with Sidekiq-Cron)
# config/schedule.yml
daily_cleanup:
cron: "0 2 * * *" # 2 AM daily
class: "DailyCleanupJob"
weekly_report:
cron: "0 9 * * 1" # 9 AM every Monday
class: "WeeklyReportJob"
# app/jobs/daily_cleanup_job.rb
class DailyCleanupJob < ApplicationJob
queue_as :maintenance
def perform
# WHY: Clean up old records to maintain database performance
OldRecord.where('created_at < ?', 90.days.ago).delete_all
TempFile.where('created_at < ?', 7.days.ago).delete_all
Rails.logger.info("Daily cleanup completed")
end
end
Sidekiq Configuration
# config/sidekiq.yml
:concurrency: 5
:queues:
- [high_priority, 2]
- [default, 1]
- [low_priority, 1]
- [mailers, 1]
# config/initializers/sidekiq.rb
Sidekiq.configure_server do |config|
config.redis = { url: ENV['REDIS_URL'] }
# WHY: Ensure database connections are properly managed
config.on(:startup) do
Rails.logger.info("Sidekiq server started")
end
end
Sidekiq.configure_client do |config|
config.redis = { url: ENV['REDIS_URL'] }
end
Testing Background Jobs
# spec/jobs/report_generation_job_spec.rb
RSpec.describe ReportGenerationJob, type: :job do
let(:report) { create(:report) }
describe '#perform' do
it 'generates report' do
expect {
described_class.perform_now(report.id)
}.to change { report.reload.status }.to('completed')
end
it 'sends notification email' do
expect(ReportMailer).to receive(:report_ready).with(report)
described_class.perform_now(report.id)
end
context 'when report not found' do
it 'logs error without raising' do
expect(Rails.logger).to receive(:error)
described_class.perform_now(99999)
end
end
end
describe 'job enqueuing' do
it 'enqueues job' do
expect {
described_class.perform_later(report.id)
}.to have_enqueued_job(described_class).with(report.id)
end
end
end
Tools to Use
Read: Read existing job filesWrite: Create new job filesEdit: Modify job logicBash: Generate jobs, run Sidekiq
Bash Commands
# Generate job
bundle exec rails generate job ReportGeneration
# Run Sidekiq
bundle exec sidekiq
# Monitor jobs (Sidekiq Web UI)
# Add to config/routes.rb: mount Sidekiq::Web => '/sidekiq'
# Clear jobs
bundle exec rails runner "Sidekiq::Queue.new.clear"
Workflow
- Identify Async Task: Determine what should run in background
- Write Tests: Test job execution and retry logic
- Generate Job: Use Rails generator
- Implement Logic: Write job perform method
- Add Error Handling: Configure retry and discard
- Configure Queue: Set appropriate queue and priority
- Test: Verify job executes correctly
- Monitor: Track job execution in production
Related Skills
rails-service-objects: Complex job logicrails-error-handling: Error handling in jobsrails-rspec-testing: Testing jobs
Coding Standards
TDD Workflow
Follow TDD Workflow
Key Reminders
- Keep jobs idempotent (safe to retry)
- Pass IDs, not objects
- Configure appropriate retry logic
- Use specific error handling
- Set queue priorities appropriately
- Monitor job execution and failures
- Test both success and failure scenarios
- Use transactions for data consistency
- Log important events
- Don't perform long-running operations synchronously
Repository

nekorush14
Author
nekorush14/dotfiles/configs/claude/skills/rails-background-jobs
2
Stars
0
Forks
Updated4d ago
Added1w ago