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.
1NoMethodError (undefined method `request_uri' for #<URI::Generic >):
2
3app/models/product.rb:8:in `download_image!'
4app/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.
1app/models/product.rb:8:in `download_image!'
2app/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.
1require 'net/http'
2
3class Product < ApplicationRecord
4 after_save :download_image!
5
6 def download_image!
7 uri = URI(image_url)
8 contents = Net::HTTP.get(uri)
9
10 File.open("public#{local_image_path}", 'wb') do |file|
11 file.write contents
12 end
13 end
14
15 def local_image_path
16 "/product_#{id}.png"
17 end
18end
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
.
1class ProductsController < ApplicationController
2 def create
3 @product = Product.new(product_params)
4
5 if @product.save
6 redirect_to @product, notice: 'Product was successfully created.'
7 else
8 render :new
9 end
10 end
11
12 private
13 def product_params
14 params.require(:product).permit(:title, :description, :image_url, :price)
15 end
16end
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.