Debugging Ruby Libraries

AuthorMáximo Mussini
·3 min read
This guide was extracted from the Debugging section I wrote for Vite Ruby.

In this post I will cover a few ways to debug code inside Ruby gems in your application.

I'll use actionview in Rails as an example, but you can apply these techniques to debug any gem.

Debugging in Ruby 🐞

There are two main debugging styles in Ruby:

  • Puts Debugging consists in adding puts statements in the target code, execute the script or application, and use the output to understand what's going on.

    def javascript_include_tag(*sources)
      puts "javascript_include_tag(#{sources.inspect})"
  • Interactive Debugging enables pausing the program at a specific instruction, examining the current state in a REPL, and doing step-by-step execution.

    def javascript_include_tag(*sources)
      binding.pry # or binding.irb or byebug

One of many advantages of interactive debugging is that it becomes possible to easily navigate into a library's internals by using step or break.

To debug a library using puts debugging, you'll need to do things differently.

Opening Gems 💎

You can run bundle open to open a gem in your favorite text editor.

The EDITOR environment variable will be used to infer the preferred text editor. I like using Sublime Text or Visual Studio Code to navigate the file tree.

For example, running bundle open actionview will open the version of the actionview gem specified in the nearest Gemfile.lock.

Editing In-Place ✍️

In contrast with compiled languages—where libraries are distributed as binaries—in Ruby most libraries are distributed as plain .rb files which are human-readable.

And human-writable! You can modify the code in a library, restart your app, and your changes will be picked up! 🤩

This is extremely useful when tracking down bugs—you can add puts or breakpoints as needed, or even modify the behavior of the library.

Debugging with Pry

Interactive Debugging by hardcoding breakpoints inside actionview

Run gem pristine :library: once you are done to undo any changes. Otherwise, you risk relying on behavior that is local and unversioned 🙀

Using a Local Library 🔗

When you need to make extensive changes a nicer flow is to clone the library, and then point to your local copy.

For example, to fix a bug in actionview you would clone the Rails repo, open it in your editor to make changes, and then update the Gemfile in your project to specify the dependency using path.

# Assuming rails was cloned in the same parent directory
gem 'actionview', path: '../rails/actionview'

If a dependency is specified using a Git repository, prefer local paths in Bundler.

Just as in the previous section, it's important to restart the program you intend to debug after making changes.

Why is it necessary to restart the application? 🤔

Most Ruby programs use require to load a library into memory, and will use that cached version during their entire running time.

Restarting the program, such as a script or a web development server, ensures that any changes you have made to the library are loaded!

Additional Resources 📖

I hope this summary has been useful! A similar guide for JavaScript is available 😃

Please refer to the following documentation for more information: