in Rails

A better Rails debugger: ruby-debug

Just a few days ago, Kent Sibilev quietly released an amazing little plugin with far reaching consequences: ruby-debug. This is a marked improvement over Ruby’s own rdebug. The major difference is that he removed the major slow down that impacted rdebug usability with Ruby on Rails so it is blazing fast.

Instead of using the Kernel#set_trace_func API, which makes it possible to implement a debugger in Ruby, but has a negative effect on the speed on your program execution, he uses a native extension with a new hook using the Ruby C API

For each trace call Ruby interpreter creates a Binding object, even though it is not being used most of the time. ruby-debug library moves most of the functionality of debug.rb to a native extension, this way significantly improving the execution of your program.

This means that watchpoints and the standard tracing facility are not supported, but I’m gladly giving that up for the comfort and speed. And if you are wondering how the speed is, that is really the difference between utter frustration each time you debug something to bliss!

Previously I covered some options for Debugging in Rails. This is going to become my preferred option by far, and I’m willing to bet this will become yours too. Why? Because you can see the source code of where you are, you can execute step while watching some or your variables, you can inspect of your variables (this you could do with breakpointer), you can use conditional breakpoints, you can get a list of expressions to be displayed every time you step. The downside compared to breakpointer? You can’t do it remotely, which in most instances, as best as I can tell, is not going to be a problem at all.

Installation

Ruby-debug comes as a gem so to install, just run:

sudo gem install ruby-debug

Make sure you chose the proper windows or ruby version depending on your platform.

Using ruby-debug

To use in your rails application, assuming you want this to only be available in development mode (this is not such a great idea to leave in in production mode, just in case you forget to remove the breakpoints, and also for performance reasons as I’ll explain in a bit)

In environement.rb add the following:

SCRIPT_LINES__ = {} if ENV['RAILS_ENV'] == 'development'

This line is important if you want to be able to see your source code. SCRIPT_LINES__ is an obscure feature of the ruby interpreter. If it is defined, it will store all loaded ruby file in a hash, which debug-ruby will use to display where you are in your source code. The only problem is that it can have some impact on performance, and worst of all, use up quite a bit of memory, which is not so good in production (hence the “if ENV[‘RAILS_ENV’] == ‘development'”). SCRIPT_LINES__ needs to be initialized as early as possible so it can capture all loaded ruby files.

To add a breakpoint, you will need to use:

require 'ruby-debug'
...
def your_method
  ...
  debugger if ENV['RAILS_ENV] == 'development'
  ...
end

Then start your app using webrick (it does not work with lighttpd and I have not investigated why just yet):

script/server webrick

When the code hits the breakpoint, you are in a console like mode (not unlike irb or script/console).

Ruby-debug commands

  • b[reak]
    list breakpoints
  • b[reak] [file|class:]LINE|METHOD [if expr]
  • b[reak] [class.]LINE|METHOD [if expr]
    set breakpoint to some position, optionally if expr == true
  • cat[ch]
    show catchpoint
  • cat[ch] EXCEPTION
    set catchpoint to an exception
  • disp[lay] EXPRESSION
    add expression into display expression list
  • undisp[lay][ nnn]
    delete one particular or all display expressions if no expression number given
  • del[ete][ nnn]
    delete some or all breakpoints (get the number using “break”)
  • c[ont]
    run until program ends or hit breakpoint
  • r[un]
    alias for cont
  • s[tep][ nnn]
    step (into methods) one line or till line nnn
  • n[ext][ nnn]
    go over one line or till line nnn
  • w[here]
    displays stack
  • f[rame]
    alias for where
  • l[ist][ (-|nn-mm)]
    list program, – list backwards, nn-mm list given lines. No arguments keeps listing
  • up[ nn]
    move to higher frame
  • down[ nn]
    move to lower frame
  • fin[ish]
    return to outer frame
  • q[uit]
    exit from debugger
  • v[ar] g[lobal]
    show global variables
  • v[ar] l[ocal]
    show local variables
  • v[ar] i[nstance] OBJECT
    show instance variables of object
  • v[ar] c[onst] OBJECT
    show constants of object
  • m[ethod] i[nstance] OBJECT
    show methods of object
  • m[ethod] CLASS|MODULE
    show instance methods of class or module
  • th[read] l[ist]
    list all threads
  • th[read] c[ur[rent]]
    show current thread
  • th[read] [sw[itch]] nnn
    switch thread context to nnn
  • th[read] stop nnn
    stop thread nnn
  • th[read] resume nnn
    resume thread nnn
  • p EXPRESSION
    evaluate expression and print its value
  • pp EXPRESSSION
    evaluate expression and print its value
  • h[elp]
    print this help
  • RETURN KEY
    redo previous command. Convenient when using list, step, next, up, down,
  • EVERYHTING ELSE
    evaluate

Happy debugging!

35 Comments

  1. You may also need to put

    Debugger.start`

    in environment/development.rb, or

    Debugger.start if ENV['RAILS_ENV'] == 'development'

    in environment.rb

  2. Jerry, you are correct. This is indeed required with the latest versions. Or use

    rdebug script/server webrick

    to start your app.

  3. Hi,

    I’ve been toying around with Eclipse for Ruby/RoR and was frustrated that I couldn’t debug using methods like you describe but with a full GUI. So I hacked something out today that allows me to use all the debugging in-line features of Eclipse on a live Rails app. Not sure if this is useful – or its speed could possibly be increased by using some of the code you describe?

    My write up on the (kludgy) method is here:

    http://www.misuse.org/cms/article.php?story=20060913182223765

    Drop me a line if this is useful!

    Steve

  4. You say add:

    SCRIPT_LINES__ = {} if ENV[‘RAILS_ENV’] == ‘development’

    to config/environment.rb. Isn’t that equivalent to adding:

    SCRIPT_LINES__ = {}

    to config/environments/development.rb? Is there any reason to do it one way or the other?

  5. It is almost equivalent, if I recall correctly, I was putting it there to get it as quickly as possible during initialization. Since it starts recording loaded files when SCRIPTLINES gets initialized. So

    I say was doing that, because with the newest versions of ruby-debug, you are much better off running your app with rdebug anyway. And you don’t need this anymore. use “rdebug script/server webrick”

  6. Do I have to place
    debugger if ENV[‘RAILS_ENV’] == ‘development’

    in all of my methods?

  7. If you want to be sure that no debugger call ends up in your production version, yes.
    A better practice, though, is to only add calls to “debugger” when troubleshooting, then remove them once you are done.
    I’ve found that using grep (or your editor’s equivalent) before you commit to make sure you’ve removed everything is a good practice.

  8. How well would it work to put the debugger call in a helper method or something like that? I know you’d start one level deeper in the stack, but you could just step out of the method call and be right back where you need to be…

  9. That’s a possibility, but I’m afraid this may get old pretty quickly as this would break the display command, the source list command to quickly know where you are, and see some key variables right away.
    Overall, being disciplined about removing calls to debugger when you are done tracking a bug is a practice that has worked for me and not let me down again (for that first few times…)

  10. haha. its funny how noone reads the comments before posting. How about this idea: Update the article instead of having everyone ask the same question.

  11. Thanks, ROger.

    In fact, installation is a lot simpler now:

    gem install ruby-debug

    I’ve updated the page.

  12. Would the configuration described above allow breakpoints when testing? Am I the only person that uses breakpoints when running my unit and functional tests?

    Thanks,
    Tom

  13. To debug my test, I usually run the test directly using rdebug. For example:

    rdebug test/unit/person_test.rb
  14. Hello,

    How I get out from ruby-debug ?

    I try “c”, but the no stop next breakpoint

  15. These days for it to work in rails do thus:

    require ‘ruby-debug’
    add the word
    debugger
    somewhere (anywhere?) in your code

  16. Nice writeup but I can’t believe you didn’t mention the use of “irb” in a debugging session. I find that more useful than all the other debugger commands combined.

    Just type irb and you get full access to the interpreter. Slick.

  17. Sure, irb is part of the arsenal, as well as script/console.

    What ruby-debug gives you that irb or console can’t, is access to the environment exactly as the request handling happens, including anything you can do in irb.

  18. You’re missing my point: use irb within a “debugger” session. That way you do get the exact environment, and the full power of ruby.

    Almost always, when I invoke debugger, I type “irb” at the ‘(rdb:1) ’ prompt.

    My point was the author neglected to mention “irb” as a Ruby-debug command.

  19. Point taken! I was not aware of that command. It did not exist at the time I wrote this page, and probably missed this from the release notes.

    Thank you for pointing that out, Stan! Should prove to be very valuable :)

  20. This syntax: require ‘ruby-debug’

    I’ve not seen these ampersand-hash-number-semicolon symbols before: are we really meant to use these, or is my browser doing something weird? (firefox in ubuntu and ie in windows both show this so i don’t think it is my browser)

  21. woah…my last post translated the symbols into single quotes when it put the comment text on your page…so there is definitely some formatting weirdness going on with your page. Just in case you don’t know what i’m on about ( :o) ), the second code example window, which is introduced with the text “To add a breakpoint, you will need to use:”

    Displays for me as (writing the name of the symbols):

    require ampersand-hash-8216-semicolon ruby-debug ampersand-hash-8217-semicolon

  22. yes, all the html entities are really quotes. That was smartypants doing something silly. Should be fixed now. Thanks!

  23. I don’t know. I haven’t used eclipse for rails in a long time. Textmate is the only thing I use really, plus vi for remote edit.

  24. Pascal, when you run the server, you probably need to pass a—debugger parameter, otherwise the breakpoints will get ignored.

  25. That was not the case when I wrote this post, but now, you’d either use – u or – – debugger directly, yes.

Comments are closed.