A hands-on tutorial to debugging your code with pry-byebug
Like most new developers, I first started as a
puts debugger. I would write
One day, I had the chance to spend some time with Cecile Varin, who showed me the ropes of pry-byebug.
So, when I taught Ruby’s basics at Le Wagon Paris, I passed that knowledge along.
Let’s dive right in, so I can give you practical tips on using pry-byebug in your applications.
Set up pry-byebug
Add the gem to your gemfile.
Before we begin, let’s build up a bit of context, so my demo is more idiomatic. I’ll draw on something that happened to me a while back.
Here’s our scenario:
- Some third-party service periodically sends us a webhook with people’s information.
- We receive and handle the data: checking for people in our database, creating them if need be, etc.
- We check that people were either matched to an existing instance or created.
Sounds good? Let’s check our code now.
First, here’s an excerpt from the data we expect to receive:
One important thing to note: this is what we expect. At that point, we only know for sure about the structure of the data. The data itself? We don’t know yet what we’ll receive.
Second, here’s our route.
Third, here’s our controller code:
What does it do:
- Loops over each person contained in the params.
- Finds or creates each person.
- Assigns the ad hoc values and persists them.
- Handles unexpected errors.
Remember when I told I’d drawn the context from my own experience? Well, here’s what happened:
After a while, my users reported some discrepancies in the app: some people sent by the third-party service were neither found nor created. No clear pattern emerged from the get-go. 🤔
‘Twas time for some
pry-byebug basic commands
Add a breakpoint and check current values:
Let’s go back to our
SomeServiceHooksController. I’ll add two breakpoints inside the loop. It’ll help me check the data at different stages.
Now, I can send the concatenated content of the last few webhooks to our controller (with Postman, for instance). Each
binding.pry will pause the execution of our code. And, right amid our server’s logs, pry-byebug will open a debugging console.
Let me show you what happens after I send the data to the endpoint:
Behold pry-byebug’s console!
=> in front of line 52? That’s pry-byebug telling you the execution paused there.
From here, you can check every variable already declared in the present context: our
params and the current
params in the console will give you the following output:
person will give you:
But soon-to-be-defined variables - like
new_person - are not accessible yet.
I can also call the methods defined in my class because Ruby reads class definitions before their execution. I can even query things from my database:
Fancy right? Let’s move on.
Execute the next line of code and wait:
So, our application paused just before
new_person’s definition. What if we want to move the cursor down one line and see what
Person.find_or_create_by(email: person[:email]) returns?
Well, we can type
next in our pry-byebug console.
See what happened? The
=> cursor moved through the definition of
new_person, and stopped just before the next line of code. So now,
new_person is accessible:
Tada! An existing instance of
Person was affected to
new_person because it existed with the email
Continue execution until the next breakpoint (or until the end of the current process):
Now, remember when I said I’d throw a couple of breakpoints for good measure? Sometimes, I want to skip big chunks of code but still pause its execution later. Think about controllers calling multiple methods, one of which is faulty. I can add breakpoint in each method to see what’s what.
In this case,
next is not enough. You’d have to type it countless times and (sometimes) navigate through your app’s stack (something that’s way too much advanced for this tutorial).
So, what should you use?
continue of course! Lemme show you:
And the output:
=> cursor didn’t just move onto the next line. It only stopped at the next breakpoint. So now, I have access to the updated version of
Once you’ve passed the last breakpoint,
continue will resume the code’s execution until the end of the current context. In our example, the current context is
SomeServiceHooksController. Since we’re in a loop,
continue will only bring you to the next
person until we looped through every people. Then, it’ll exit the
SomeServiceHooksController class and get on with its life.
Want to exit pry-byebug the dirty (my) way? Try
exit!. It’ll kill both the current process and the server. This is useful when having too many breakpoints across multiple places.
⚠️ A warning: since
binding.pry’s pause code execution, don’t push ‘em into production. Or you’ll be in for a rough time. 😬
How my debugging ended up: never trust your database
When I learned to code, a lot of people told me to never trust users’ input. Well, no one ever told me to never trust my database either!
Remember when I told you that I was only sure about the structure of the data sent through the webhook? Well, it turned out that some people’s data were incomplete:
Lesson #1: never trust your users’ input
You would expect
Person.find_or_create_by(email: nil) to return
nil, right? Well, it turned out some of my oldest instances of
Person had been created before I had a validation of presence in place for their email.
So, every time the third-party service would sent me a person with a
nil email, I would update the first instance of
Person with an
email == nil instead of creating a new one (or handling this as an error). These people fell from the net for weeks before I managed to identified the core problem.
Lesson #2: never trust your own data
Well, that’s it for today folks! I hope it’ll make your debugging more enjoyable!
Noticed something? Ping me on Twitter or create an issue on GitHub.
P.S.: This post was featured in Ruby Weekly’s 496 edition.