Debug Your Rails App With ruby-debug

At the start of this year, I published a book through SitePoint titled Build Your Own Ruby On Rails Web Applications. You’ll forgive me if I’m a little biased, but it is, I believe, still the best beginner’s Rails book out there, despite the fact that many other books on Rails have been released since.

One of the best things about the Ruby on Rails framework is that it’s undergoing constant change — it’s continually being improved and tweaked. Unfortunately, this change means that anyone who purchased my book when it was initially published will have been faced with a quandary over which version of Ruby to use:

  • You could follow the book’s advice and stick with Ruby 1.8.4. This would allow you to work your way happily through the book (including the chapter on debugging), but it would also mean that you wouldn’t receive the benefits of the bug fixes and security updates that come with having an up-to-date installation of Ruby.
  • Alternatively, you could upgrade to a more recent version of Ruby. Doing so, however, would have prevented you from using the breakpointer client — a tool to which much of Chapter 11 is devoted.

At the time of the book’s publication, the breakpointer client was the best tool available for debugging a Rails application. However, breakpointer is not compatible with the latest version of the Ruby programming language.

So what’s a Rails developer to do?

In this article we’ll look at an alternative debugging tool for debugging Rails applications (or any Ruby script, for that matter) while still remaining up-to-date with the latest version of the Ruby interpreter. This tool is even slated to be bundled with future releases of Rails.

The magic new tool to which I’m referring is called ruby-debug.

A quick warning: this isn’t a beginners’ article. I’ll assume that you’ve either read my book, or have a solid understanding of how to develop a web application using Ruby on Rails. Ideally you’ll have Rails installed and will be comfortable with using the Interactive Ruby (irb) shell. It will also help if you’re familiar with shovell, the sample application used in the book; download the sample to play along at home (note: the download is 2.2MB in size).

What Broke Breakpointer?

Before we delve into the inner workings of ruby-debug, let’s look briefly at why a tool as useful as breakpointer came to be incompatible with the language for which it was designed to be used.

The breakpoint library, of which the breakpointer client is a part, was developed and released to the public by Florian Gross. It was built on top of a Ruby extension called binding.of_caller, which relied on a bug in Ruby’s implementation of trace calls. This bug, however, was fixed in Ruby 1.8.5! The result is that both the binding.of_caller extension and the breakpoint library became unusable. The library is also no longer being maintained, so it’s effectively dead.

Say Hello to ruby-debug

Back in July of 2006, Kent Sibilev released the first version of ruby-debug, describing it as "a faster implementation of the standard debug.rb." Today, ruby-debug is the most viable option with regards to debugging Ruby scripts — and that includes Rails applications.

While it would be beyond the scope of this article for me to explain how ruby-debug works its magic behind the scenes, it suffices to say that ruby-debug uses a natively compiled Ruby extension that’s written in C. The result is that it performs amazingly well, even with very large Ruby scripts.

Comparing Feature Sets

As I described in the tutorial in my book, the breakpointer client allowed us to step into our application at run time and explore it from the command line. While the ruby-debug tool includes similar functionality, it goes way beyond what good old breakpointer had to offer.

Unlike breakpointer, which worked from a simple irb prompt, ruby-debug provides you with a more advanced shell, similar to that provided by GDB, the GNU debugger for the C programming language.

In this shell you can:

  • Step forward and backward in your code.
  • Execute and skip lines of code (without copying and pasting them from your code editor window).
  • List the actual source context at which you’ve stopped your application.
  • Step into irb mode and make use of the same shell that’s used by breakpointer (if you’re someone who finds old habits difficult to shake).
Installing ruby-debug

The following steps will configure your system for debugging with ruby-debug.
First of all, we need to install the ruby-debug library. Since it’s distributed as a RubyGems package, the installation is as easy as typing the following command:

$ sudo gem install ruby-debug -y

The install script will prompt you to indicate the platform on which you’re installing the gem. If you’re on a Mac or a Linux system, select option 1; if you’re on a Windows machine, select option 2. The installation process should look similar to that shown in Figure 1.

Installing ruby-debug

Y is for Yes, Please!
By invoking the gem command with the -y switch, we instructed the utility to automatically install all other packages upon which ruby-debug depends. In this case, those packages include the ruby-debug-base package containing the actual debugger code; the ruby-debug package contains the CLI (command line interface).

With ruby-debug happily installed, we can now place some hooks in our Rails application. For this example, we’ll use the completed shovell application from the book as an example of the process of debugging with ruby-debug. If you’d like to follow along with this example, you should download shovell, the sample application, before reading any further.

In Rails

Making a Rails application aware of ruby-debug is as simple as adding a line to the application’s config/environments/development.rb file:

# config/environments/development.rb 
require "ruby-debug"

It makes sense to make this change to our development environment (as compared to, say, config/environment.rb), as this is the preferred environment in which you should perform your debugging. The aim should be to replicate any errors that occur in other environments, so debugging should rarely occur in a testing or production environment.

ruby-debug is part of EdgeRails
Many developers like being on the bleeding edge, and are happy to develop their applications with a version of Rails that’s not guaranteed to be as stable as the official release, in order to receive the very latest feature additions. This version of the framework is referred to as EdgeRails.

Explicitly adding ruby-debug to your application environment is unnecessary if you’re running EdgeRails — simply start the application server with the --debugger argument to load the ruby-debug library.

With this setting in place, you can fire up your application server from your Rails application’s root folder:

$ ruby script/server

Figure 2 shows the console output that results when we start the Mongrel server.

Starting the Mongrel application server

Bricks and Mongrels Only
If your development environment makes use of an alternative server, such as lighttpd with FastCGI, you won’t be able to use it as a debugging platform. You’ll need to be running either WEBrick or Mongrel.

At first glance, the console output doesn’t look much different from what we’re used to seeing when we run this command. However, keep a close eye on this window as we progress through this exercise.

Debugging an Application

To see our debugger in action, we’ll take a look at a problem that I’ve secretly introduced to the application’s code. If you look at Figure 3, you’ll notice that although we’ve provided a description for the new story we submitted, it doesn’t show up on the final story page. If you’ve read the book, you’ll notice that this example is identical to the example in Chapter 11; instead of solving it with breakpointer, we’ll use ruby-debug.

: Story description missing from a newly submitted story

So, let’s crack the ruby-debug whip at this problem. First, add the debugger keyword to the new action in app/controllers/story_controller.rb, like so:

def new  
 @story = Story.new(params[:story])  
 @story.user = @current_user  
 
 if request.post? and @story.save  
   debugger  
   @story.tag_with params[:tags] if params[:tags]  
   flash[:notice] = "Story submission succeeded"  
   redirect_to :action => 'index'  
 end  
end

As was the case when we used breakpointer on this problem, when you try to submit a story, you’ll now experience the "hanging browser syndrome", which indicates that your debugger statement has kicked in and you’re ready to debug.
Instead of firing up a separate client to connect to the inner workings of your application, ruby-debug has opened this debugger shell right inside the terminal window in which you’ve fired up your application server, as shown in Figure 4.

 The ruby-debug interactive prompt appears within the server console

From this prompt you can explore your application while it’s paused mid-execution, using a variety of commands. Throughout this example, I’ll indicate the ruby-debug shell prompt using the characters (rdb), and commands typed at this prompt will appear in bold, as follows:

(rdb) list
The ruby-debug Commands

What follows is a quick rundown of the most important ruby-debug commands, along with a brief description of what they do. Don’t worry too much about remembering every last detail — the built-in help command will list all the available commands for you. You can also use the help <commandname> syntax to get help with a specific command.

  • backtrace: Display a trace of the execution stack, similar to what is displayed when your application raises an exception.
  • break/delete: Display a list of breakpoints that have been set in your application. This command is also used to set new breakpoints, or delete existing ones, from within the ruby-debug shell.
  • cont: Leave the current debugger shell and resume execution of the application until the next breakpoint is encountered.
  • irb: Invoke an interactive Ruby interpreter at the current point of execution, similar to the shell used by the breakpoint library.
  • list: Display the code fragments surrounding the current point of execution. (We’ll make use of this command in a moment.)
  • method/method instance: Explore the available class methods and instance methods, respectively.
  • next/step: Continue execution one step at a time — this is a huge improvement over the breakpoint library.
  • p/pp: Short for print and pretty print respectively, these commands can be used to evaluate Ruby expressions and display the value of variables to the console.
  • quit: Exit the debugger. Note that this will also exit the application server if it was invoked from the command line, as demonstrated above. To just exit the current debugging session, use cont.
  • reload: Reload the Ruby source files from disk. This can be useful if you’ve changed class definitions and want to reload them dynamically without leaving the current debugging session.

For a list of all available commands and options, use the help command.

Moving Around in the Shell

Now that we’ve been dropped into a shell, it’s time to make use of some of the commands we just discussed to get to the root of our problem (which is that our stories are ending up without descriptions).

First of all, let’s find out exactly where we are in the execution of our story submission. This is the job of the list command, as shown in Figure 5.

The list command displaying the current location in a paused application

As you can see, the list command displays a source code listing with an arrow pointing to the line of code that is next to be executed.

At this point, we can examine parts of the working environment from the irb shell, such as the @story instance variable or the params hash. Type irb at the prompt, and let’s investigate the description attribute of the story object that’s stored in our @story variable:

(rdb) irb  
irb> @story.description  
=> nil

The output of inspecting this variable is shown in Figure 6.

Using irb from within ruby-debug to inspect a variable

As you can see, even though we’ve entered a beautifully phrased story description into the form, the relevant description attribute of the new story object is nil, or empty.

But hang on a minute! Isn’t there a command in ruby-debug that allows us to evaluate Ruby expressions and inspect variables without going through the hassle of using irb? There sure is! Let’s exit the irb shell (using the command exit) and continue poking around from outside the shell.

Back in the native ruby-debug shell, we can use the pp (pretty print) command to display the value of our story’s description once it’s populated through the web form:

(rdb) pp params[:story][:description]  
=> nil

If you type this into your ruby-debug shell, you’ll see that it also returns an empty object. So, as a last resort, let’s take a peek at the full params hash, which contains the values of all form fields that have been submitted, no matter which scope they reside in:

(rdb) pp params

Using the pp command to inspect variables

As you can see, pp actually formats the output for us to make it more readable than the output we’re used to seeing in the standard irb shell (hence the word "pretty"). While this feature is not exclusive to ruby-debug (the pp library can be loaded outside of ruby-debug as well), it’s certainly convenient that ruby-debug makes use of it automatically.

The section I’ve highlighted in Figure 7 is the root of the problem. As you can see, the description is indeed present in the params hash, but it’s not part of our story. While the story’s name and link attributes are sitting nicely together in the params[:story] hash, the description is sitting separately in params[:description].

Now, how did that happen? If we take a look at our form template (located at app/views/story/new.rhtml) it appears that I’ve "accidentally" deleted a few characters:

new.rhtml
 
# Wrong:  
<p>  
 description:<br />  
 <%= text_area :description, :rows => 10 %>  
</p>

new.rhtml
# Wrong:  
# Right:  
<p>  
 description:<br />  
 <%= f.text_area :description, :rows => 10 %>  
</p>

Instead of going through the FormBuilder object that the form_for helper provides, my code was calling text_area without applying the scope of the form field. As a result, the description was ending up as a separate entry in the params hash, and our story was never receiving its value.

But What About All the Fancy Tools in ruby-debug?

Admittedly, we haven't had to use any of ruby-debug's more advanced features to debug this example problem. But when we're forced to debug more complicated code, ruby-debug's advanced features become really handy.

Let's first take a look at the stepping methods. To do so we'll need to move our debugger statement into a method that contains a little more code than the previous example (so that we can actually step through each line). The best candidate for this task is the vote action of our StoryController; here's a version of this method to which I've added the debugger statement:

story_controller.rb (excerpt)
 
def vote  
 debugger  
 @story = Story.find(params[:id])  
 @story.votes.create(:user => @current_user)  
 
 respond_to do |wants|  
   wants.html  { redirect_to :action => 'show', :permalink => @story.permalink }  
   wants.js    { render }  
 end  
end

To invoke the debugger in this new location, exit your current debugging session (if you haven't already) using the cont command. This will resurrect your stalled browser and allow you to continue browsing the shovell application. Now, select a story from the upcoming stories queue and click the Vote! button to engage the debugger once more.

Previously, we saw how the list command could be used to give us an indication of where in the source code our application was currently paused. When it's paused, we can use the next command to advance to the next line of code. Typing next will display the regular Rails log output for the following line, then return you to the ruby-debug prompt. From here you can once again use list to check your new location in the application, as I've done in Figure 8.

Using next to advance one line of code

To explore the methods provided by an object that you're curious about, you can use the method command. The following command will produce a list of instance methods provided by the @story object, sorted alphabetically, as shown in Figure 9:

(rdb) method instance @story

Using the method command to display an object’s instance methods

The method command can be used to list class methods, too. The following command will produce an alphabetically sorted list of class methods provided by the Story class, as shown in Figure 10.

(rdb) method Story

Listing the class methods for the Story class

Manually Setting Breakpoints

While using the next command can be useful if you know exactly where in your application to go poking around, it can be less useful in a Rails application. The level at which the stepping occurs can in some circumstances be far too granular, and can result in your stepping through multiple lines of core library files instead of your own code.

To gain a little more control over where the debugger halts execution, you can manually set breakpoints at the locations you desire. Breakpoints can be set by specifying either:

  • a combination of filename and line number
  • a class name and the name of an instance method or class method

As a practical example of setting manual breakpoints, we're going to move the halt point from its current location (inside the vote action of StoryController) to the RJS template that's rendered when that same action is requested to render a JavaScript response. We'll do all of this without ever opening a text editor, or stepping over every line between the current point of execution and the code of the RJS template.

The last line of the RJS template at app/views/story/vote.rjs reads:

vote.rjs (excerpt)   
 
page[:vote_history].replace_html :partial => 'vote',  
 :collection => @story.latest_votes

It therefore makes sense to choose the latest_votes instance method of a Story object as our new breakpoint. To do so, execute the following command in the ruby-debug shell:

(rdb) break Story#latest_votes

You can now let go of the current breakpoint by typing the cont command in the ruby-debug shell. Execution will resume until the @story.latest_votes call is executed, at which point the application will pause again. To verify that we're paused exactly where we expect to be, type list. Figure 11 confirms that I've stopped my application at the beginning of the latest_votes method.

Stopping at a breakpoint that was set by specifying class and method name

Source-code Reloading

A Rails application, when run in development mode, automatically adopts all changes that are made to the source files without requiring you to restart the application server. ruby-debug includes a similar feature to avoid stale code passages from being displayed in the stack traces and listings output by the list command. If you can afford it (performance-wise), type the following at the ruby-debug prompt:

(rdb) set autoreload

With this setting, ruby-debug will automatically reload your Ruby scripts from disk whenever necessary. If this appears to slow down your development progress significantly, you can instead periodically invoke the reload command whenever you think you're getting stale representations of your code.

TextMate Integration

Those developers who develop their Rails applications on a Mac using the TextMate editor will be pleased to know that the author of ruby-debug happens to be a fan of TextMate as well. Fortunately, ruby-debug ships with some nice hooks that you can use to integrate the debugger with the editor.

First of all, you can open the file in which your application is currently paused using the tmate command. This eases the round-trips between your terminal window and your editor quite a bit.

I'd also recommend that you install the Ruby Debug Bundle for TextMate. This package gives you ultimate control over setting breakpoints from within TextMate itself.

After you've installed the bundle, you'll need to launch your application a little differently. Here's how to start your application server to take advantage of ruby-debug's remote debugging facilities (the $ indicates that we're typing this in an operating system shell):

$ rdebug -sn ./script/server

Unlike the local debugging facility that we've been using thus far, you can safely minimize the terminal window in which you started your application server -- we need to fire up a separate debugging client (from a new command prompt) to enable communication between TextMate and ruby-debug.

Open a new terminal window and type the following command:

$ rdebug -c

You should see the message "Connected", as shown in Figure 12.

You'll need to leave this window open and accessible, as this will be the window that displays the ruby-debug console output once a breakpoint is encountered.

Using the TextMate Ruby bundle to connect to ruby-debug’s remote debugging tool

As soon as that's accomplished, you can use the bundle's only keyboard shortcut, Cmd-Shift-B, to open up a menu of all the available commands, shown in Figure 13.

Displaying the TextMate Ruby bundle's menu options

You can use this menu to set a breakpoint at the position of the cursor in the current TextMate editing window. You can also show or delete all breakpoints that have been set, or even interrupt or quit the debugging shell right from the convenience of your text editing window.

Conclusion

ruby-debug is a worthy successor to the old (and admittedly hackish) technique of debugging your Ruby application using the breakpoint library. Better yet, it's compatible with all the most recent releases of the Ruby language interpreter. ruby-debug comes packed with many welcome shortcuts and powerful navigation commands that make debugging Ruby scripts and Rails applications a joyful and rewarding experience.

For further reading on ruby-debug and many helpful articles and links to Ruby resources, I thoroughly recommend you subscribe to Kent Sibilev's weblog.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments

Comments on this post are closed.