By Steve Carey - 3/3/2018
Below are Ruby debugging techniques that have worked best for me. Rails has built techniques like logs and exceptions into it's code base so knowing how to leverage them will be useful when building a plain Ruby or a Ruby on Rails application.
Create logs at different points in your code that print status and useful information to the terminal window that your server is running on. Do that with the puts method. If you have an error you can look at the logs to see what the last successful log message was. That way you know your code worked up to that point. The below example prints some environmental variables when a client sends a request to view a web page. This logs the request method, request path, and the current date and time:
class Application def call(env) ... puts("Started #{request.request_method} \"#{request.path}\" #{Time.now}") ... end end
And this log prints the parameters of a request when a controller object is created. The \s space character and the space after it will indent the log two spaces.
class Controller def initialize(request) @request = request puts("\s Parameters #{@request.params}") end
If we get invalid input from the user, we want to provide them feedback so they can correct it. So from our code's perspective this is not an error. We handle it with conditional statements. For instance if we have a Routing class that checks the URL path a user entered and it doesn't match any valid route, we can use an if/else statement to return a 404 Not Found Response:
def dispatch if URL path is valid code for handling a valid URL path else Rack::Response.new(["Nothing found"], 404, {"Content-Type" => "text/html"}) end end
The above example will handle the user error of entering an invalid URL. But what if there is a programming or other unexpected result that prevents the page they requested from being displayed? Well, we still want to send them a message saying that something went wrong. And we want to log the error so we can trace its source.
def dispatch if URL path is valid code for handling a valid URL path else Rack::Response.new(["Nothing found"], 404, {"Content-Type" => "text/html"}) end rescue StandardError => error puts("#{error.class.name}: #{error.message}") puts("Exception source stack displays filename, line number, and method the exception is in:") puts(error.backtrace) Rack::Response.new(["Internal error"], 500, {"Content-Type" => "text/html"}) end
The keyword rescue will start a block where you can handle an exception. In this case we are sending a response to the client that just says "Internal Error" with status code 500. But we are also using puts statements to print the error information to our terminal for debugging.
Ruby's overall exception class is Exception. But within that there are multiple subclasses and subclasses within those. The full list is at ruby-doc.org - Exception
An exception means anything unexpected, generally some kind of error. Rescue applies to the StandardError class (and it's subclasses) by default, but you can specify a different exception class to apply it to.
Like everything in Ruby, the exception is an object. The keyword rescue gives us access to that object and => variable name
assigns that exception object to the variable name we give it, such as "error." Then we can access the error object properties like class.name, message, and backtrace, and log them to our terminal using puts statements. Class.name let's us know what error class it is. Message will give us a (hopefully) helpful message to pinpoint the source of the error. And backtrace will return the filename, line number, and method that was the source of the exception. There will be a whole stack of these, with the top one being the direct source of the error, and below that are the methods affected by the error, in order.
It is good practice to try to pinpoint the type of errors you might get and create different rescue blocks if you want to handle them differently. And you can even create your own exception class and inherit from StandardError or RuntimeError.
If you want to limit the section of code to apply your rescue to, then use a begin block before the rescue block:
def dispatch some code you don't want rescued begin code that may need rescuing rescue code to execute if there is a StandardError exception. end end
More info:
Ruby Docs: docs.ruby-lang.org - exceptions
Informative Blog post: blog.honeybadger.io - Beginner's Guide to Exceptions in Ruby
Okay, you have an error and you need to trace it. Follow these steps:
If the Exception logs didn't help you then look at your regular logs to see how far your code progressed before the error. So you will know it was working up to the last expected log message. Then you can use temporary puts statements or even better, the Byebug gem.
You can insert temporary puts statements in your code to print out variables or just to confirm that your code makes it to a certain point.
class Controller ... def dispatch ... puts "At Controller#Dispatch" end end
Then once you've fixed your bug, delete the puts statement.
Homepage: github.com/deivid-rodriguez/byebug
Byebug is a very useful gem for debugging. Rails automatically adds it to the Gemfile of a new Rails project. You only want this gem in your Development and Testing Environment though. Not in Production.
# Gemfile group :development, :test do gem 'byebug' end
bundle install
To use it you simply add byebug
as a statement line in your code. Put it just above the statement you think may be causing the error.
You may also need to require it in the file you are debugging.
require 'byebug' class ClassName ... def method_name ... byebug ... end end
Then when you run your program it will stop when it hits byebug
. Go to the terminal window that is running your server and you will see a <byebug> shell where you can run Ruby code from the point you are in. And you can step through the code line by line by typing in n
for next. Below are a few handy commands and methods you can use:
As mentioned above, Rails automatically adds the byebug gem to your Gemfile when you start a new Rails project. But Rails also adds another gem called web-console to your development environment:
# Gemfile group :development do gem 'web-console' ... end
This is a debugging tool like Byebug except it puts an IRB shell at the bottom of the web page. It does that on exception pages, and, similar to Byebug, wherever you insert console
in your code. You can also put it in a view file using erb: <%= console %>