academy

Debugging exceptions in Rails

Jeff Kreeftmeijer

Jeff Kreeftmeijer on

Debugging exceptions in Rails

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.

shell
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.

shell
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.

ruby
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.

ruby
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.

Become our next author!

Find out more

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
AppSignal monitors your apps