Fix: ActiveRecord::RecordNotFound — Safe Handling in Rails

Problem

Your Rails app crashes with a 404-style database error:

ActiveRecord::RecordNotFound (Couldn't find User with 'id'=9999)

This shows the user a 500 error page or a raw exception — not ideal for production.

Symptoms

  • User.find(params[:id]) raises exception when the record doesn’t exist
  • API returns 500 instead of 404
  • Error tracking (Sentry, Bugsnag) floods with RecordNotFound exceptions
  • Nested resource lookups fail silently or crash

Root Cause

Rails’ find method raises ActiveRecord::RecordNotFound when no record matches the given ID. Unlike find_by, which returns nil, find treats a missing record as an exceptional condition. The default Rails exception handler treats uncaught exceptions as 500 errors, which is semantically wrong for a missing resource.

Solution

Option 1: Use find_by with explicit nil check

# ❌ Crashes on missing record
@user = User.find(params[:id])

# ✅ Returns nil, handle gracefully
@user = User.find_by(id: params[:id])
if @user.nil?
  redirect_to users_path, alert: "User not found."
  return
end

Option 2: Rescue in the controller

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  private

  def set_user
    @user = User.find(params[:id])
  rescue ActiveRecord::RecordNotFound
    redirect_to users_path, alert: "User not found."
  end
end

Option 3: Global rescue with rescue_from

class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, with: :record_not_found

  private

  def record_not_found
    respond_to do |format|
      format.html { render file: "#{Rails.root}/public/404.html", status: :not_found }
      format.json { render json: { error: "Not found" }, status: :not_found }
    end
  end
end

This handles RecordNotFound across all controllers automatically.

Option 4: Use find_by! with rescue_from

find_by! raises RecordNotFound just like find, but lets you use non-ID columns:

@post = Post.find_by!(slug: params[:slug])
# Raises RecordNotFound if no post with that slug exists

Verification

# Test in console
User.find(9999)
# ActiveRecord::RecordNotFound: Couldn't find User with 'id'=9999

User.find_by(id: 9999)
# => nil

# With rescue_from set up, visit a non-existent record URL
# GET /users/99999
# Expect: 404 page, not a 500 error

Prevention

  • Use find_by for lookup from user input — IDs from URLs are user input
  • Use find only when the record must exist (e.g., within a transaction after creating it)
  • Add rescue_from ActiveRecord::RecordNotFound to ApplicationController early in every project
  • Set up custom error pages for 404 and 500 in public/404.html and public/500.html
  • Filter RecordNotFound from error trackers if you handle it with a 404 — it’s expected behaviour, not a bug

References


Advertisement