My puts Debugging Workflow in Rails Apps

 
Ruby on Rails puts debugging workflow is represented by a magnifying glass Photo by Agence Olloweb on Unsplash

Puts debugging is the best. In this tutorial, I’ll walk you through my workflow for debugging Rails codebases using a custom toolkit inspired by Rust. We will also discuss how to smuggle your debug tools, through even the most rigorous code review process.

Why and how to puts debug Ruby code?

I’ve never been a fan of (or properly learned) using debug breakpoints. While I sometimes use ruby debug, most of the time, I’m a puts debugger.

My favorite way to debug is via an automated test suite. I usually have the retest gem running in the background to automatically execute relevant tests on each file save. If I need to narrow the scope, I use a shortcut to copy the current filename with the line number and run a single test:

rs spec/models/user_spec.rb:42
rs is a alias for bundle exec rspec


So far, I’ve relied on quick-and-dirty print statements like these:

p '!!!!!!!!!!!!!!'
p 'first_user', first_user
p 'second_user', second_user
p '!!!!!!!!!!!!!!'

Please don’t tell me you haven’t written something like this before. It highlights a few issues with the default way to debug in Ruby.

!!!!!!!!!!!!!!! - annotation is often needed. p debug statements don’t stand out enough in a verbose test/logs output.

p 'first_user', first_user - manually adding labels is necessary to identify which debug statement is which.

p method calls are hard to clean up later since they might already exist elsewhere in the codebase.

Introducing dbg-rb - a simple Ruby puts debugging helper

Recently, I’ve been writing more Rust than Ruby. I really came to appreciate its built-in dbg! macro:

#[derive(Debug)]
struct User {
    username: String,
    password: String,
    active: bool,
}

fn main() {
    let user = User {
        username: "user1".to_string(),
        password: "password1".to_string(),
        active: true,
    };

    dbg!(user);
}

// [src/main.rs:15:5] user = User {
//     username: "user1",
//     password: "password1",
//     active: true,
// }

It produces a verbose, source-located output, together with the name of the variable. Coming back to Ruby, it felt odd that such a simple and handy helper isn’t available out of the box.

Hence, the birth of the dbg-rb gem. It’s my attempt to recreate the Rust dbg! macro in Ruby:

dbg(User.last(2).map(&:as_json)) 

dbg-rb debug output

It prints the filename, line number, and source expression — all with colored output for quick scanning. It also works for method and keyword arguments. For me, knowing the source location and seeing labeled variables names makes debugging faster and far more pleasant to work with. What’s more, the dbg method is unique, so it’s trivial to grep and clean up after use.

For more details and configuration options, check out the documentation in the dbg-rb repo.

How to use dbg-rb in Rails without adding it to the Gemfile?

I find dbg-rb useful in my everyday Rails work. But I understand that not all the devs will share this enthusiasm. When doing performance audits, I needed a way to use this tool without cluttering the dependency list — especially in teams that are strict about gem additions.

In theory it’s possible to add dbg-rb to the IRB config to include it in the Rails console context. But it has little value; I rarely debug directly in the console. I use a test suite for that. It means that dbg-rb has to be available in the bundle exec context. But how can you add a gem to the Gemfile without modifying it?

In theory, I could use a local development branch with a custom Gemfile, but cherry-picking changes before submitting a PR is cumbersome.

Here’s a hack that I came up with. It’s similar to my previous post on Rails debug aliases. You can inject dbg-rb into a Rails app via a local initializer file that’s ignored by Git globally.

Start by creating the file with the following contents:

~/.gitignore_global

config/initializers/dbg_rb.rb

Now configure your git to use it:

git config --global core.excludesfile ~/.gitignore_global

Next you can add an inlined lite version of the dbg-rb file to your Rails app initializers folder:

wget https://raw.githubusercontent.com/pawurb/dbg-rb/refs/heads/main/inline/dbg_rb.rb -O config/initializers/dbg_rb.rb

That’s it! You can now use dbg to debug local variables and parameters with automatically appended labels:

dbg user 
# [spec/user_spec.rb:20] user = #<User id: 1, team_id: 1, slack_id: "develop", pseudonym: ...

None of these steps leaves a trace in the git diff, so you can apply it to any Ruby/Rails project without a code review process. Debug tools like this are safe for local use. But be cautious if you’re introducing less obvious techniques — always consult your team before adding anything bypassing standard workflows.

Summary

I find the additional context provided by dbg-rb gem helpful for non-trivial debugging sessions. And if you’re a “vibe debugger”, feeding this extra info to the LLMs increases your chance to quickly produce a correct answer. I wish a similar tool was available by default like in Rust. Unfortunatelly, this Ruby feature proposal has received little traction so far. If you have suggestions for improving dbg-rb — feedback, PRs, and bug reports are always appreciated.



Back to index