Ruby Debugging

Ruby Debugging

Beginner

Pry vs. Byebug vs. Ruby-Debug (Choosing a Debugger)

The first thing you should do is get comfortable with “falling into a debugger.” That means you put a breakpoint at the place where you want to debug. Typically, before the line of code where you crash is. And then run your specs.

Your running Ruby process will “fall into the debugger” — that means, it will stop executing your Ruby code and the debugger simply sits at a prompt waiting for you to tell it what to do. From there, if you type a variable name and hit enter, the debugger will typically evaluate & print the variable. (The fact that it evaluates and prints is an important computer science concept that often causes some subtle effects that you will learn as you learn the craft more deeply.)

“Dropping into your debugger” means when Ruby hits that line of code, it will pause the execution and hang right there without continuing. Be sure to note what that looks like because your browser will simply be frozen until you explicitly tell Ruby to continue.
You’ll want to switch between your browser and your shell prompt where you are running Ruby

You can also do something call step (or sometimes step over) which means you tell Ruby to execute one line of code and then wait for your next instruction. If that line of code happens to be a method, since you told Ruby to step over exactly one line of code, it will execute the entire method (including all of its lines of code, child methods, etc), come back up into the point where you are in the debugging callstack, and then wait for your next instruction.

If, on the other hand, you are at a breakpoint and the next line of code is a method that you want to go into and debug inside, or step into, you’re gonna do just that: step into. Notice the subtle distinction between step over and step into.

Finally, when you’re done debugging, you’ll typically type c for continue. That means the debugger should stop waiting for your instructions and just keep runing the rest of your program until it naturally either exits or crashes.

Remember, the concepts above apply all debuggers in all languages! The specific syntax you will use to do the above commands will be specific to the actual debugger you choose.

Here are the three primary debuggers used in the Ruby community:

PryByebugRuby-Debug
How to drop into the debuggerbyebugbinding.b
Compatible with Zeitwork?
(hot reloads your Ruby files as you change them)
NOYES
When calling to a shell, you may get a deadlock like
eval error: No live threads left. Deadlock?

Byebug

deivid-rodriguez/byebug

Byebug is a fantastic debugger available for Ruby 2 (and presumably above). Drop gem ‘byebug’

into your Rails app Gemfile and bundle install.

In either your test run or your development run (that is, while you’re running a test or testing with the browser or as I covered yesterday, Postman), write byebug in your Ruby code.

on a single line of your app and voila. When you hit that line, you will drop into the debugger.

If you’re not developing a Rails app, you can reqiure 'byebug' at the top of your Ruby file.

Full docs here.

Ruby Debug

With Rails 7, the default debugger is now ruby/debug.

Tools, Tricks, and Essential Ruby For Debugging

pretty print (pp)

Pretty print is one of my favorite introspection tools to help see variables more clearly.

You write it on the console as pp. It prints out your object with each attribute on its own line. Take for example this Country object, shown here on the Rails console without pretty print

2.4.6 :010 > x
=> #<Country id: 232, iso_name: "UNITED STATES", iso: "US", iso3: "USA", name: "United States", numcode: 840, states_required: true, updated_at: "2019-05-19 17:16:07", zipcode_required: true>

Now, with pretty print, the same object is conveniently displayed with each attribute as its own line. This is invaluably helpful when you have deep nesting of objects.

2.4.6 :009 > pp x
#<Country:0x00007fd8507ea358
id: 232,
iso_name: "UNITED STATES",
iso: "US",
iso3: "USA",
name: "United States",
numcode: 840,
states_required: true,
updated_at: Sun, 19 May 2019 17:16:07 UTC +00:00,
zipcode_required: true>

Debugging, debugging, debugging.

puts, .to_s, and .inspect

OK, so we get a 3-in-1 here: When you call puts on an object, .to_s will be called and then output to your screen. So you should make your objects have a .to_s that is human readable, possibly even for use in, say, a drop-down menu or label.

class Person
 attr_accessor :first_name, :last_name

 def to_s
   "#{first_name} #{last_name}"
 end
end

.inspect, on the other hand, is specifically for developers. In this method, you would write out as much information as you the developer (or next developer) want to see, including the keys (ids) of your objects if those will be helpful:

class Person
 attr_accessor :first_name, :last_name

 def inspect
   "Person id: #{id} - first: #{first_name}; last: #{last_name}"
 end
end

Your objects should have both .to_s and .inspect on them, and you can try these universally named Ruby methods on other people’s objects to examine them. A well-formed codebase implements them or has a helpful output for both of these to help you with your debugging.

.to_yaml


Pretty print’s cousin is the .to_yaml method, which will take your object and convert it into YAML. Take for example this arbitrary object, which you will notice contains a :ghi key that has a nested object as its value:

2.4.6 :023 > x= {abc: 1, def: 4, ghi: {ye: 6, nm: 3}}
=> {:abc=>1, :def=>4, :ghi=>{:ye=>6, :nm=>3}}
2.4.6 :024 > x
=> {:abc=>1, :def=>4, :ghi=>{:ye=>6, :nm=>3}}

.to_yaml on its own will produce a string that will output with newline characters, like so:

2.4.6 :025 > x.to_yaml
=> "-\n:abc: 1\n:def: 4\n:ghi:\n :ye: 6\n :nm: 3\n"

To make this more useful, try puts along with .to_yaml

2.4.6 :026 > puts x.to_yaml
-
:abc: 1
:def: 4
:ghi:
 :ye: 6
 :nm: 3

x.method(:_____).source_location

(where :_____ is name of the method — as a symbol — you are trying to search for)

OK, so the ultimate secret tool of Ruby debugging is this little-known method that will magically — and I mean magically — tell you where a method was defined. That’s right, I mean the actual line number itself.

2.2.5 :002 > a.method(:hello)
=> #<Method: Apple(Thingy)#hello>
2.2.5 :003 > a.method(:hello).source_location
=> ["/Users/jason/Projects/nokogiri-playground/app/models/thingy.rb", 2]

Look, ma, take a peek into my hard drive and you would find that the hello method is actually defined on the file at the full path /Users/jason/Projects/nokogiri-playground/app/models/thingy.rb on line 2.

Like magic it works for Rails and Gem code too, and is invaluable when you are ready to dive into the APIs you are working with.

x.methods

By default, this method will return a list of all of the methods on an object. Watch out because you’ll get all the methods on the ancestor chain too.

In older versions of Ruby, you could use this method to examine the instance methods that were defined on this class only (excluding the ancestors), but unfortunately this no longer works.

If you pass this method false, like so: x.methods(false)

…things get more interesting: then you get only the class methods defined on this object’s class itself. (Remember in Ruby class methods are defined with self.)

Active Record Query Trace

brunofacca/active-record-query-trace

An excellent gem that’s still a non-optional workhorse in my development practice – especially when debugging a legacy codebase. This gem will display for ALL of your SQL queries where in your Ruby or Gem code the active record update commands are coming from.

Follow the instructions in the Gem to create an initialize file and set it up. My only tip here that adds to the docs is that you’ll want to set the number of lines: ActiveRecordQueryTrace.lines = 10

When debugging a problem in my own Rails app I want this set to a lower number (like 5 or 10). When debugging a problem in Gem code or in Rails I need this at a much higher number (like 50 or 100).

flyerhzm/bullet

Understanding N+1 queries is a significant litmus test that sets amateurs from the professionals. Bullet is like a magic bullet – literally named so. It finds your N+1 queries.

Bullet is a great gem that you should install in either development or test, not in production. Because it adds overhead to your speed, install it but leave it configured so that it is turned off by default. Any developer on the team who needs it can turn it on.

You CAN and SHOULD turn it on periodically too, to examine where your app is producing N+1 queries.

Here’s the catch with Bullet. Active Relation creates queries as chains of conditions before it translates that to and executes SQL. That’s why when you do this you must carefully consider query = Country.where(name: "United States")

When you do this in your Rails console, you will see it run the SQL right away.

2.4.6 :039 > Spree::Country.where(name: "United States")
 Spree::Country Load (0.7ms) SELECT `spree_countries`.* FROM `spree_countries` WHERE `spree_countries`.`name` = 'United States' LIMIT 11

Observer Effect

It only does this because of the ‘print’ effect the Rails console has on your objects. If you string another .where onto this object, when translated into SQL, ActiveRecord will combine the queries: query = Spree::Country.where(name: “United States”); query.where(iso: “US”);
 Spree::Country Load (1.0ms) SELECT `spree_countries`.* FROM `spree_countries` WHERE `spree_countries`.`name` = ‘United States’ AND `spree_countries`.`iso` = ‘US’ LIMIT 11

The reason this is important is that to understand where your N+1 queries are coming from you need to understand when you created when you invoked them. They are not the same place, although on the Rails console it makes you think it is one and the same. When you grok this, you will see why Active Record’s side loading (which loads a related set of objects in a single optimized query, taking the number of queries from N+1 to 1+1=2) is both efficient and can be tricky to work with, especially with objects that have many relationships.

Don’t be fooled: Side-loading is nearly always more efficient than N+1 queries. 

Bullet tells you where those pesky N+1 queries are invoked, but not where you are creating them. What you then need to do is trace your code (manually) to figure out where the queries are being created. Hopefully, this should be near in the code to where they are being invoked (but in the case of complex filtering logic might not be).

Here you need to add the appropriate .joins(:____) to your code anywhere between when you set up the objects and when Active Record invokes them. Note that you’ll only want to join in those additional tables if you actually use them. If not, you don’t need the overhead.

You’ll know you’ve solved your N+1 queries because you won’t see them output in your Rails log, and they’ll disappear from the Bullet code.

Introspect, Introspect, Introspect but remember Ruby’s last-line quirk

Always look at what you’re doing. Drop into your debugger>. Look at your variables. Clone & freeze them. Look for race conditions, Look for flip-floppers. Watch out for your own confirmation bias.

Remember that when in the debugger or on the Rails console and you type a SINGLE VARIABLE and HIT RETURN, the console will interpret that action as-if you had called .inspect

2.2.5 :007 > a
=> #<Apple id: nil, created_at: nil, updated_at: nil>
2.2.5 :008 > a.inspect
=> "#<Apple id: nil, created_at: nil, updated_at: nil>"

observer effect” in software development

In some cases, the act of inspection actually changes the object (like in the case of an Active Relation, in which case it invokes the query), so keep that in mind. (We might call this the “observer effect” in software development.)