APM Academy

Debugging Exceptions in Rails: Common Errors and Fixes

Debugging Exceptions in Rails: Common Errors and Fixes

When a Rails request fails, the exception’s class, message, and backtrace tell you where to look. Rails 8.1 logs the error — ActiveRecord::RecordNotFound, ActionController::ParameterMissing, or NoMethodError: undefined method 'strip' for nil — followed by the application frames that raised it. This guide decodes the most common Rails exceptions: what each message means, why it happens, and how to fix it.

This article traces one real exception from the log to its fix, then works through the five exceptions you’ll meet most often — each with the exact message Rails writes to log/development.log, its usual causes, and the fix (for reading the trace itself, see Reading and Understanding Ruby Stack Traces). Every log block comes from a real Rails 8.1.3.1 application on Ruby 3.4.

Tracking Down Exceptions Using the Stack Trace

Someone creates a product in our example shop application, and the request dies with an HTTP 500. This appears in log/development.log:

Shell
NoMethodError (undefined method 'strip' for nil):

app/models/product.rb:9:in 'Product#download_image!'
app/controllers/products_controller.rb:17:in 'ProductsController#create'

Read the first line first: the exception class is NoMethodError, and the message says something called .strip on nil. That doesn’t name the culprit yet, so we move to the stack trace, which Rails has cleaned down to the frames inside our application.

The trace reads top-down: the first frame is where the exception was raised, and each frame under it is the caller of the one before. So the error happened on line 9 of app/models/product.rb, inside Product#download_image!, which was reached from ProductsController#create. Opening app/models/product.rb:

Ruby
require "net/http"
 
class Product < ApplicationRecord
  validates :name, presence: true
 
  after_create :download_image!
 
  def download_image!
    uri = URI(image_url.strip)
    contents = Net::HTTP.get(uri)
 
    File.binwrite("public/product_#{id}.png", contents)
  end
end

Line 9 is uri = URI(image_url.strip). The only .strip call is on image_url, so image_url must have been nil for this product. Because download_image! is an after_create callback, it runs as part of saving a new record — which explains the second frame: the controller’s create action triggered the callback by saving the product. Here’s app/controllers/products_controller.rb:

Ruby
class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
 
    respond_to do |format|
      format.html
    end
  end
 
  def new
    @product = Product.new
  end
 
  def create
    @product = Product.new(product_params)
 
    if @product.save
      redirect_to @product, notice: "Product was successfully created."
    else
      render :new, status: :unprocessable_content
    end
  end
 
  private
 
  def product_params
    params.expect(product: [:name, :image_url])
  end
end

Line 17 is if @product.save — the call that ran the callback. And product_params explains how image_url ends up nil: params.expect(product: [:name, :image_url]) requires the product key but treats the attributes inside it as optional. A browser form submits an empty string for a blank field, but a request that omits the parameter entirely — an API client, a script, a mobile app — leaves the attribute nil. We can reproduce the exception with one request:

Shell
curl -X POST -d "product[name]=Ruby" http://localhost:3000/products

That confirms the diagnosis, and it shows a side effect: because after_create runs inside the save’s transaction, the exception rolls the INSERT back, so no half-created product is left behind.

The fix depends on the requirements. If every product needs an image, validate it, so the save fails cleanly instead of crashing:

Ruby
validates :image_url, presence: true

If the image is optional, guard the callback instead:

Ruby
def download_image!
  return if image_url.blank?
 
  uri = URI(image_url.strip)
  # ...
end

A presence validation won’t catch every bad value (a non-URL string still breaks the download), but it turns a 500 into a validation error the user can fix. That’s the whole method: exception line first, then the application frames top-down, then out to the caller until you find the value that didn’t match your assumptions.

Common Rails Exceptions and Their Error Messages

Some exceptions come up so often that recognizing them on sight saves the walk through the code. Each entry pairs the logged message with what it means, its usual causes, the fix, and the HTTP status Rails renders in production. The development debug page uses the same status codes.

NoMethodError (undefined method '...' for nil)

Shell
NoMethodError (undefined method 'strip' for nil):

app/models/product.rb:9:in 'Product#download_image!'
app/controllers/products_controller.rb:17:in 'ProductsController#create'

Meaning: The receiver of the method call was nil, not the object you expected. Ruby names the method (strip here); the trace names the line.

Causes: A find_by or association that returned nil, a database column that was never set (like image_url in the walkthrough), or a typo — a misspelled instance variable silently evaluates to nil. In the console and in Ruby 3.4’s terminal output, error highlighting underlines the exact call in the offending line; the log keeps only the message and trace.

Fix: Find out why the value is nil at that line, then either guarantee it (validation, database constraint) or handle the nil case explicitly. NoMethodError has no special handling in Rails, so an unrescued one renders a 500.

ActionController::ParameterMissing (param is missing or the value is empty or invalid)

Shell
ActionController::ParameterMissing (param is missing or the value is empty or invalid: product):

app/controllers/products_controller.rb:27:in 'ProductsController#product_params'
app/controllers/products_controller.rb:15:in 'ProductsController#create'

Meaning: params.expect(product: [...]) (or the older params.require(:product)) looked for a product key in the request parameters and found it missing or empty. With expect, a product key holding the wrong shape — a plain string where a nested hash is expected — raises the same exception, which is the “or invalid” case.

Causes: Form fields that aren’t nested under the expected key (name instead of product[name]), a flat JSON body like {"name": "..."} instead of {"product": {"name": "..."}}, or a key spelled differently in the form and the controller.

Fix: Make the request’s nesting match what the controller expects. form_with model: @product nests fields correctly on its own; hand-built forms and API clients need the wrapper key. Rails maps this exception to a 400 Bad Request.

ActiveRecord::RecordNotFound (Couldn't find Product with 'id'=...)

Shell
ActiveRecord::RecordNotFound (Couldn't find Product with 'id'="999"):

app/controllers/products_controller.rb:3:in 'ProductsController#show'

Meaning: Product.find(params[:id]) found no row with that id. The value appears in double quotes because URL parameters arrive as strings; in the console, Product.find(999) produces Couldn't find Product with 'id'=999.

Causes: A stale link or bookmark to a deleted record, an id typed into the URL by hand, or a scoped lookup like current_user.products.find(...) that correctly hides another user’s record.

Fix: Often, nothing — Rails maps unrescued RecordNotFound errors to a 404, which is the right answer for a missing public record. When a missing record is an expected outcome you want to handle, use find_by (which returns nil) or rescue the exception and redirect; rescuing exceptions in Ruby covers when rescuing yourself is the right call.

ActiveRecord::RecordInvalid (Validation failed)

Shell
ActiveRecord::RecordInvalid (Validation failed: Name can't be blank):

Meaning: A bang method — save!, create!, update! — hit a failing validation and raised instead of returning false. The message lists every failed validation, so it doubles as the diagnosis.

Causes: Bang methods in controllers, jobs, or service objects running against data that doesn’t satisfy the model’s validations. The non-bang variants (save, create, update) return false instead of raising, which is why controller actions usually branch on if @product.save and render the form again on failure.

Fix: Either switch to the non-bang method and show the validation errors, or fix the data before saving. Rails maps RecordInvalid to a 422 — a status Rack 3.2 renamed: the :unprocessable_entity symbol is deprecated (it still renders 422, with a warning) in favor of :unprocessable_content. ActiveRecord’s rescue mapping already uses the new symbol, and render :new, status: :unprocessable_content is the current form.

ActionController::UnknownFormat

Shell
ActionController::UnknownFormat (ActionController::UnknownFormat):

app/controllers/products_controller.rb:5:in 'ProductsController#show'

Meaning: The request asked for a format the action doesn’t offer — here, GET /products/1.json against a respond_to block that only declares format.html. The message is the class name repeated, so the trace carries all the information: the frame points at the respond_to call.

Causes: API clients requesting .json or .xml from HTML-only actions, crawlers probing URLs with format extensions, or a respond_to block missing a format the app links to.

Fix: Add the format to the respond_to block if the action should serve it; otherwise leave it, since Rails maps this exception to a 406 Not Acceptable. A lookalike carries the descriptive message instead: when an action has no respond_to block and no view file at all, a browser request raises ActionController::MissingExactTemplate (BareController#show is missing a template for request formats: text/html), also rendered as a 406. That one means a missing view file, and the fix is creating the template. (A non-browser request to the same action renders a 204 No Content instead of raising.)

Each of these exceptions is quick to diagnose when you can trigger it yourself in development. In production, you meet them after the fact — AppSignal’s Rails error tracking captures the exception class, message, and full backtrace for every occurrence and groups them by controller action, so the debugging moment starts with the evidence already collected.

For where those captured exceptions should go and how to route them, see error reporting for Rails exceptions.

Wrapping Up

Rails exceptions follow one shape: class, message, and application frames. Once the five messages in this reference are familiar, most debugging sessions start at the fix instead of the search. To report these exceptions from your own code with context, the AppSignal exception handling documentation shows the API.

Happy debugging!

P.S. If you’d like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!

Frequently asked questions

What does param is missing or the value is empty or invalid mean in Rails?
Rails raises ActionController::ParameterMissing when require or expect cannot find the parameter key, or its value is empty or the wrong shape. Check that your form or request nests fields under the expected key, and that the key is spelled the same in both places.
Why does Rails say undefined method for nil?
A NoMethodError for nil means the receiver of the method call was nil, usually a missing record, an unset attribute, or a typo in a variable name. Ruby 3.4 underlines the exact call in the message, so start there, then check why the value is nil.
How do I fix ActiveRecord::RecordNotFound in Rails?
RecordNotFound means find could not locate a row with the given id. If missing records are expected, use find_by and handle nil, or rescue the error. Rails maps unrescued RecordNotFound errors to a 404 response, so public pages fail gracefully by default.
What HTTP status codes do common Rails exceptions return?
Rails renders RecordNotFound as 404, ParameterMissing as 400, UnknownFormat as 406, and RecordInvalid as 422, now named unprocessable_content. Anything unmapped, including NoMethodError, becomes a 500. In development you see the debug error page instead; the status codes appear in production.
How do I find what caused a 500 error in Rails?
Open the request’s entry in the log: Rails prints the exception class, its message, and the application backtrace frames that raised it. The top frame is where the error happened; the frames under it show which controller action led there. In production, an error tracker captures this for you.

Published , Updated

Wondering what you can do next?

  • Share this article on social media

Become our next author!

Find out more
$appsignal install

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal