All posts

Rails Error Handling Patterns for Production

Implement robust Rails error handling with rescue_from, custom exception classes, background job error recovery, and API error responses.

Rails Error Handling Patterns for Production

Rails has opinions about error handling, but production apps need more than the defaults. Here are patterns that work at scale.

Centralized Controller Error Handling

class ApplicationController < ActionController::API
  rescue_from ActiveRecord::RecordNotFound, with: :not_found
  rescue_from ActiveRecord::RecordInvalid, with: :unprocessable
  rescue_from ActionController::ParameterMissing, with: :bad_request

  private

  def not_found(exception)
    render json: { error: exception.message }, status: :not_found
  end

  def unprocessable(exception)
    render json: {
      error: 'Validation failed',
      details: exception.record.errors.full_messages
    }, status: :unprocessable_entity
  end

  def bad_request(exception)
    render json: { error: exception.message }, status: :bad_request
  end
end

Custom Exception Classes

module Errors
  class ServiceError < StandardError
    attr_reader :code, :status

    def initialize(message, code: 'INTERNAL_ERROR', status: :internal_server_error)
      @code = code
      @status = status
      super(message)
    end
  end

  class RateLimitExceeded < ServiceError
    def initialize
      super('Rate limit exceeded', code: 'RATE_LIMITED', status: :too_many_requests)
    end
  end
end

Background Job Error Handling

class ProcessOrderJob < ApplicationJob
  retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 5
  discard_on ActiveRecord::RecordNotFound

  def perform(order_id)
    order = Order.find(order_id)
    OrderProcessor.new(order).process!
  rescue PaymentGateway::Error => e
    order.update!(status: 'payment_failed', error_message: e.message)
    raise # Re-raise for retry
  end
end

Service Object Pattern

class CreateOrder
  def call(params)
    ActiveRecord::Base.transaction do
      order = Order.create!(params)
      process_payment(order)
      send_confirmation(order)
      order
    end
  rescue Stripe::CardError => e
    OpenStruct.new(success: false, error: e.message)
  end
end

Key Principles

  • Let exceptions bubble up — handle at the controller level
  • Use `rescue_from` in ApplicationController — consistent error responses
  • Retry transient failures in background jobs — network errors are temporary
  • Never rescue `Exception` — only rescue StandardError and subclasses

Integrate Bugsly for real-time error tracking. Rails' default error pages hide production issues; Bugsly surfaces them immediately with full request context.

Try Bugsly Free

AI-powered error tracking that explains your bugs. Set up in 2 minutes, free forever for small projects.

Get Started Free