How to Debug & Fix Your Ruby Programs

How often does your program do exactly what you want the first time around?

Many times our programs dont’t work like we expect, so we have to use the art of debugging ruby to help us finding out why.

You may be familiar with the following error message:

undefined method 'some_method' for nil:NilClass

This means that a nil value managed to find it’s way into our code.

Using the techniques discussed in this article you will learn how to deal with this issue and similar problems!

Understanding Errors & Stack Traces

When you are getting an error from the Ruby interpreter or your program is not doing what it should be doing then it’s time to put on your debugging hat.

If the problem is that your program is crashing, it is important to pay attention to the error message, which usually will contain clues of what’s going wrong.

Here is an example:

def method1
  method2
end

def method2
  puts invalid_variable
end

method1

Running this code will give you the following error:

/tmp/stack.rb:6:in 'method2': undefined local variable or method 'invalid_variable' for main:Object (NameError)
    from /tmp/stack.rb:2:in 'method1'
    from /tmp/stack.rb:9:in '
'

This is what is known as a stack trace.

Let’s analyze it together!

We start with the line on the top.

This is where the actual error occurred, but it doesn’t mean the error condition originated here.

However, it is a good point to start our investigation.

Here’s the deal:

Text Description
/tmp/stack.rb:6 File and line number
in `method2 Method name
undefined local variable or method ‘invalid_variable Error message
main:Object Class name
(NameError) Exception name

As you can see the error is not that intimidating when broken down in this way.

By the way, you can find a list of exceptions here.

Now:

Every line in the stack trace below the first one tells you how the code got here.

It’s basically a method chain, if you keep going down you should eventually find the main method of your app.

Here is a general algorithm for dealing with a stack trace:

  1. Read the top line of the stack trace
  2. If the file is part of your project: open the faulting file on the indicated line number. If it isn’t, keep going down the stack trace until you find the first reference to a file you recognize
  3. See if anything obvious jumps out to you and fix it (look for things mentioned on the error message)
  4. If that doesn’t help then you will need to find more information, like the values of the affected variables.

Debugging Ruby

The most basic (which doesn’t necessarily mean bad) debugging technique that you are probably familiar with is just dumping the values of the suspected variables.

In Ruby you can do that using puts or p.

Using p is equivalent to saying puts variable.inspect, and it’s useful for looking at objects.

Example:

Book = Struct.new(:title)

def find_book(title)
  books = []
  books << Book.new('Eloquent Ruby')

  books.find { |b| b.title == title }
end

book = find_book('Eloquent Ruby')
p book # This will print our book object

book = find_book('POODR')
p book # This will print nil

book.name # Guess what happens next!

Digging Deeper with Pry

When you have many variables to check, adding puts everywhere might not be very practical.

In that case you should try pry.

Using pry you can make your code stop at a specific line of code (also known as a breakpoint) and it will drop you into an irb-like environment, where you can evaluate ruby code in the context of your project, or execute one of the many useful pry commands.

Using pry is really easy:

All you have to do is drop binding.pry where you would like to install a pry breakpoint.

You will also need to require pry into your project (require 'pry').

If you just want to do it temporarily then you can call your ruby script like this:

ruby -rpry app.rb

That won't be very helpful for a rails app, so you may want to add pry to your Gemfile.

What I like to do is to have a macro/snippet on my editor that already includes the require in the same line than the breakpoint, so when I delete it I will be deleting both things.

This is what you will see when you are dropped on a pry session:

debugging ruby

If you want to completely quit a pry session you can type exit!, if you do a regular exit it runs your program until the next breakpoint.

The power of pry doesn't end here. For example, you can use the ls command to see what methods and instance variables an object has access to.

pry debug

Don't forget to run the help command to get a listing of all the goodies!

Another Ruby Debugger: Byebug

Byebug can act as a pry replacement or as a gdb-like debugger for Ruby.

If you want to use it for the former then you just drop byebug instead of binding.pry where you want your code to stop. One of the cons of using Byebug over pry is that it doesn't provide syntax highlighting.

Let's see how you can set breakpoints and debug you code inside byebug!

Usually you would call the help command, but in this case it is lacking a bit on information:

ruby debugger

So you will have to consult the documentation.

You can see how using the command break and a line number you can set your breakpoints.

To get a list of breakpoints you can use info breakpoint.

Once your breakpoints are set, you can move through the program execution using the following commands:

  • step (advance one instruction, stepping into method calls)
  • next (advance one instruction, doesn't get inside methods)
  • continue (run until the end or next breakpoint)

If you type enter without any command it with just repeat the last one, this is very useful when walking through your code.

When All Else Fails

Make sure to take a break when you have put a good amount of time in and can't see the solution, when you come back with fresh eyes you will realize the solution was in front of you. You can also try explaining the problem to someone else.

Some times you are not sure where the problem is, when this happens you still have plenty of options.

For example, you may want to comment blocks of code to try and isolate the issue.

If the issue disappears then you can uncomment a portion of the code that you just commented.

This is a very low-tech solution, but it might be exactly what you need.

If you got this far and nothing seems to help:

It's time to pull out the big guns.

Here are some system tools that you will often find to be helpful.

One of these tools is Wireshark, which will let you inspect network traffic.

If you are dealing with SSL-encrypted traffic a mitm (Man in the middle) proxy like mitmproxy might be able to help you.

You can also try curl to initiate HTTP connections from your terminal, which may help you debug invalid server responses.

Another tool that is useful to be familiar with is strace (linux only).

Strace will show you all the system calls that your app is doing.

You can filter for specific system calls using the -e option. A more modern alternative to strace is sysdig.

Warning! You may want to avoid using strace in production since it severely degrades the performance of the system under test.

Finally, if you are dealing with an issue that looks like it's coming from some external gem, an obvious step is to inspect the gem's source code.

You can use the gem open command to open the source code on your configured editor.

Conclusion

Even if debugging isn't the most fun activity ever there are plenty of tools and techniques that can make it easier for you, use them to help you.

Please share this post if you enjoyed it so more ppl can learn! 🙂

Thank you.

8 thoughts on “How to Debug & Fix Your Ruby Programs”

  1. Great post!
    I don’t know if you are familiar with the tv serie “Mr. Robot”, but I would like to share some thoughts about debugging:

    “Most coders think debugging software is about fixing a mistake, but that’s bullshit. Debugging Is actually all about finding the bug, about understanding why the bug was there to begin with, about knowing that its existence was no accident. […] A bug is never just a mistake. It represents something bigger, an error of thinking.” ~ Eliot.

    I think Eliot has a point in there, and the things you wrote about here can help us understand why we make errors when coding and how to undo them.

    • Thank you for your comment! I have never seen “Mr. Robot” but I have heard about it. I really like that quote you posted, so I guesss I will have to watch the show now 🙂

  2. Using p(obj) instead of puts(obj.inspect) is not preferable just because of brevity. If need be, #inspect can be overridden. The idea is to dump the object itself, e.g. String#inspect wraps an object in quotes (like so – “self”). It can be very useful on instances of your own classes if you get stuck.

    Excellent choice of the subject! Loved the tip for “require pry” snippet.

Comments are closed.