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
LoadError is derived from a different hierarchy headed by