The Ins and Outs of Debugging Ruby with Byebug
Ruby on Rails is, arguably, one of the finest innovations in modern web development. Its astute focus on simplicity and developer productivity, combined with user satisfaction, precipitated a culture of rapid prototyping with a heavy focus on unit testing (even if DHH has since revised his position.) Rails has, no doubt, been a catalyst for many successful startups (Twitter among them). One of the main criticisms levelled at it (particularly from people who are less experienced in using it) is the heavy use of ‘magic’. As Ruby developers, we are acutely aware that magic is simply DSLs and metaprogramming, but that can still prove frustrating to debug. Enter: Byebug.
The de-facto debugger for Rails is Byebug. It works with Rails where things like Pry fall short, comes recommended by the core Rails team, and is even bundled with Rails. This article will walk you through getting set up with basic debugging along with some slightly more advanced techniques.
Getting Set Up
Version 5.0 of Rails comes with Byebug, but if you don’t have it, you can simply add the following to your Gemfile and run
I’ve taken the liberty of knocking up a quick Rails project to debug here . It is a simple app demonstrating a broken quicksort algorithm, and we’re going to use Byebug to fix it.
Quicksort performs at O(n log n), utilising a divide-and-conquer approach to sorting an array (indeed, it is so performant that Ruby uses it internally for its
array.sort method). I could wax lyrical about sorting algorithms all day, but my editor will shout at me, so we’d better get back to ByeBug:
Clone the project:
git clone https://github.com/disavowd/quicksorter.git
Run the following:
cd quicksorter && rails s
Here’s what you should see when you navigate to http://localhost:3000:
Unsorted: [77, 22, 66, 28, 39, 4, 54] Sorted: [4, 28, 85]
And here’s the rather contrived example that has led us here:
def quicksort(array) return array if array.length <= 1 pivot_index = (array.length / 2).to_i pivot_value = array[pivot_index] array.delete_at(pivot_index) lesser = Array.new greater = Array.new array.each do |x| if x <= pivot_value lesser << x else greater << x end end return quicksort(lesser) + [pivot_value] - quicksort(greater) end
You can enter byebug in a similar fashion to other debuggers: pick a point in the code at which to jump into the debugger and add:
...code... byebug ...code...
...code... debugger ...code...
I’ve already done this for you in a separate controller, so if you navigate to http://localhost:3000/debug, you’ll get kicked straight into ByeBug in your terminal, which will look exactly like this:
28: def quicksort 29: @sorted = quicksort_algorithm(@unsorted) 30: end 31: 32: def debug 33: byebug
=> 34: @sorted = quicksort_algorithm(@unsorted)
35: render ‘quicksort’
Not the most intuitive interface. So what do we do now? Well, here are the byebug commands:
n (next) – executes the next line of code.
next is great, but it’s not going to step into a function for you. It will simply execute the function without walking you through the code.
For the times you want to go line by line, use:
s (step into) – This will continue onto the next stack frame and jump you to the corresponding source. Speaking of source:
l (list) – This outputs the source code around the currently executing line. You can pass
- to it to see the code before it. You can also pass start and end line numbers separated with a hypen:
l 2-6 will show the code in the currently executing file from lines two to six.
c (continue) – This will continue the program’s execution until it either concludes or it hits another breakpoint.
pp (pretty print) – ‘Pretty Prints’ variables. Invaluable when you’re dealing with nested hashes or other slightly more complex data structures.
q (quit) – This will exit Byebug and return execution control back to the program.
It’s easy to forget with all this control over execution that we can examine exactly what’s on the stack:
Will show you that the self is currently the
ApplicationController object. What methods does it have?
m stands for
method and will yield:
debug quicksort quicksort_algorithm
That concludes the basics. Let’s look at a more practical example relating to our problem.
Our quicksort is sorting the elements just fine – it’s just losing half of them somewhere along the way. Wouldn’t it be great if we could set a conditional breakpoint? Oh wait… We can! Let’s kick into Byebug when the pivot value is equal to the middle value in the array we see in the browser and have a poke around.
Add the following to line 25:
byebug if pivot_value == 28
Now at the byebug console, type:
You should see:
[77, 66, 39, 54]
Well, those certainly look like our missing numbers! The culprit is obviously the errant
- on line 25, so changing it to
+ will yield a functional (if slightly naive!) quicksort algorithm.
As this contrived example neatly illustrates
byebug is just a regular Ruby method call, which means that it is subject to all the great things that Ruby provides when you harness its Object Model. Learning both when and which conditions to attach to your breakpoints is a tremendous boon to productive Byebug use.
Other Advanced Techniques
f (frame) – shows information about the currently executing stack frame, including both the file and line number. This also takes an integer, so you can see what will be executing in a couple of stack frames time, for example:
th (thread) – allows you to check information relating to, and to interact with threads.
stop have all been helpful to me in the past. Additionally, you can pass a number to switch context to the corresponding thread.
hist (history) – will show all of your history. This (helpfully) persists between debugging sessions.
save – saves byebug history into a file (you can specify this with an argument, or let it default to
.byebug_save in your home directory)
source – This is one of the best features for power users, in my opinion – if you have a bunch of Byebug commands, aliases or custom functions that you find yourself using frequently, you can source them directly into your debugging session.
irb – This will kick you into an irb session.
bt (backtrace) – This displays the backtrace.
info – This is one of the most versatile commands. Pass
locals to it for more information about what is in scope.
file will show you which file is currently executing, a line count, a list of the breakpoint positions, a last modified time, and an SHA1 digest. This is absolutely invaluable when you aren’t sure if your changes have propagated.
This may be a solid primer for debugging with Byebug, but we’ve barely scratched the surface. We haven’t looked at breakpoints and catchpoints, traversing the program stack or displays. For more information, I recommend this excellent cheatsheet, or better yet, just digging around and playing with it. It’s fairly intuitive after the initial learning curve and you should find this a valuable addition to your developer toolbelt.
One of the great things about Byebug is that it works wonderfully in tandem with other programs. The excellent pry-byebug adds
break commands to pry using byebug. Additionally, one of my default gems on any project in which I’m using minitest, is the fantastic minitest-byebug. This will kick you straight into a Byebug session in the event that one of your tests fails. With a little work, you can make this play nicely with Guard, to create a formidable unit testing tool. Finally, for you Sublime users, there is sublime_debugger, which wraps Byebug in a neat little GUI.