A Cross-version Debug Pattern for Ruby

As circumstances would have it, I work with a few projects that need to be compatible with both Ruby 1.8 and 1.9. I also like to use a debugger, and getting one to work seemlessly under both versions isn’t exactly intuitive, so I wanted to share a nice pattern that I’ve been using recently in my personal projects.

The traditional debugger ruby-debug has been known to be 1.9 incompatible for some time now, but more recently, its updated version ruby-debug19 is no longer 1.9 compatible having been broken by 1.9.3 without a new release. Luckily, the awesome new debugger gem stepped in to fill the gap.

Include both debuggers in your Gemfile with platform conditionals:

group :development, :test do
  gem "debugger",   "~> 1.1.3",  :platforms => [:ruby_19]
  gem "ruby-debug", "~> 0.10.4", :platforms => [:ruby_18]
end

I debug pretty often, but don’t like to type a lot, so I usually include a shortcut in my test_helper.rb to get a debugger invoked quickly regardless of the Ruby version that you’re running:

def d
  begin
    require "debugger"
  rescue LoadError
    require "ruby-debug"
  end
  debugger
end

Now drop it into a file like so:

def requires_frequent_debugging
  risky_call rescue nil
  Singleton.manipulate_global_state
  d # the debugger will start on the next line
  Model.do_business_logic
  super
end

It might seem like the debugger would start in the d method rather than where you want to debug, forcing you to finish the stack frame before you could start debugging. Fortunately, that’s not the case. The d method has returned by the time the debugger is invoked, leaving you exactly where you want to be.

In a classic case of open-source overkill, I’ve extracted the pattern described above into a trivial gem called d2. Throw it in your Gemfile, make sure that your project is either using Bundler.setup or including require 'd2' somewhere, then use d2 somewhere to trigger the debugger.

Aside – A slightly interesting Ruby tidbit related to the code above is that we use rescue LoadError because a generic rescue only catches StandardError exceptions. LoadError is derived from a different hierarchy headed by ScriptError.

Posted on June 9, 2012 from Berlin

About

My name is Brandur. I'm a polyglot software engineer and part-time designer working at Heroku in San Francisco, California. I'm a Canadian expat. My name is Icelandic. Drop me a line at brandur@mutelight.org.

Aside from technology, I'm interested in energy and how it relates to our society, travel, longboarding, muay thai, symphonic metal, and the guitar.

If you liked this article, consider finding me on Twitter.