Logo of AppSignal

Menu

Debugging exceptions in Rails

Jeff Kreeftmeijer on

When an error happens in your Rails application, the exception and stack trace help you find where the problem occurred. After knowing what happened where, we need to find out why it happened. In this article, we’ll go over using the backtrace to find a bug in a Rails application.

1
2
3
4
NoMethodError (undefined method `request_uri' for #<URI::Generic >):

app/models/product.rb:8:in `download_image!'
app/controllers/products_controller.rb:5:in `create'

In this example exception, we received a NoMethodError with undefined method `request_uri' for #<URI::Generic > as its message. Since this exception doesn’t immediately tell us what the problem is, we’ll need to inspect the stack trace to find out what happened.

1
2
app/models/product.rb:8:in `download_image!'
app/controllers/products_controller.rb:5:in `create'

Looking at the stack trace, we learn the exception was raised from a download_image! method on the Product model. We’ll continue our investigation in the code, and we’ll work our way down the stack trace to find out what’s going wrong.

Opening the model shows that line 8 (where the exception was raised from) calls Net::HTTP.get(uri), so it looks like that uri is not the object we expect it to be.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require 'net/http'

class Product < ApplicationRecord
  after_save :download_image!

  def download_image!
    uri = URI(image_url)
    contents = Net::HTTP.get(uri)

    File.open("public#{local_image_path}", 'wb') do |file|
      file.write contents
    end
  end

  def local_image_path
    "/product_#{id}.png"
  end
end

Since the download_image! method is an after_save callback, we know it’s executed immediately after saving a new Product.

The uri variable is built from a method named image_url on line 7. To find out where that comes from, we’ll take another look at the stack trace to see the Product#create method is called from ProductsController#create.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ProductsController < ApplicationController
  def create
    @product = Product.new(product_params)

    if @product.save
      redirect_to @product, notice: 'Product was successfully created.'
    else
      render :new
    end
  end

  private
    def product_params
      params.require(:product).permit(:title, :description, :image_url, :price)
    end
end

Aha! ProductsController#create creates a new product with the product_params, which include the :image_url parameter we were looking for.

We know the image_url attribute is used to build the broken URI. If we leave the image_url field empty when creating a new product, we can successfully reproduce the problem.

In this case, creating URI with an empty string as its value results in a URI::Generic object instead of a URI::HTTP, because it can’t determine the URL’s format. Since the former doesn’t have a #request_uri method, it raises a NoMethodError from Net::HTTP.get.

Depending on the project’s requirements, adding a validation to make sure the field is not empty could fix the issue. Only validating to make sure the image URL is not empty won’t fix all possible problems with this implementation (we’ll still get an exception when the passed value is not an URL, for example), but it’s a good start.

Tracking down exceptions using the stack trace

Rails’ logs provide a great way to debug issues. Although the raised exceptions don’t always make a lot of sense on first glance, carefully retracting the steps the code took to get to the issue is usually a great way to find out what went wrong, even if the source of the problem is buried a little deeper in your app.

We’d love to know how you liked this article, if you have any questions about it, and what you’d like to read about next, so be sure to let us know at @AppSignal.

10 latest articles

Go back

Subscribe to

Ruby Magic

Magicians never share their secrets. But we do. Sign up for our Ruby Magic email series and receive deep insights about garbage collection, memory allocation, concurrency and much more.

We'd like to set cookies, read why.