When RubyMine's Debugger Beats puts: A Practical Case for Real Breakpoints
puts debugging makes you guess which variable matters before the run. RubyMine's graphical debugger—breakpoints, Evaluate Expression, conditional stops, and remote attach for Docker—lets you inspect everything after the pause. Here is when it is worth the switch, and when it is not.
11 Jul 2026, 05:57 UTC

Every Ruby developer has done it: sprinkle puts or Rails.logger.debug through a method, rerun the code, scroll the output, delete the lines, and repeat. It works, but it is slow, and it punishes you every time you guess the wrong variable to print. RubyMine ships a graphical debugger—built on the ruby-debug-ide and debase gems—that replaces that loop with breakpoints, stepping, and live expression evaluation. This post makes the case for when that switch is worth it, and when it is not.
What you actually get over puts
The core difference is that a breakpoint pauses the program in place, with the full state of that frame available. Instead of predicting which values matter before the run, you inspect everything after the pause: local variables, instance variables, the call stack, and object internals.
Three capabilities do most of the work:
- Line and exception breakpoints. Click the gutter to stop on a line, or configure the debugger to break whenever a specific exception is raised—even one that gets rescued somewhere you did not expect.
- Stepping. Step over, step into, and step out let you walk the actual execution path, which is invaluable when a bug turns out to be in a callback or a gem you assumed was fine.
- Evaluate Expression and Watches. While paused, you can run arbitrary Ruby in the current frame. This is the feature that most directly kills the edit-rerun cycle: you can test a hypothesis (
user.orders.where(status: :pending).sum(:total)) without touching the source file.
A worked example: the nil that should not be nil
Say a Rails mailer intermittently raises NoMethodError: undefined method 'name' for nil. With puts debugging you would log the user, rerun, discover the user exists, log the association, rerun again, and so on.
In RubyMine instead:
- Open the mailer and click the gutter next to the failing line to set a breakpoint.
- Run the mailer (or the failing test) via the green Debug icon. For a standard Rails or RSpec configuration this is one click—RubyMine wires up the debug gems itself.
- When execution pauses, open Evaluate Expression (Alt+F8 on Windows/Linux, Option+F8 on macOS) and probe:
@order.user, then@order.user_id, thenUser.exists?(@order.user_id).
In one pause you can discover the user was soft-deleted—something that might have taken three or four puts cycles to isolate.
For hot loops, use a conditional breakpoint: right-click the breakpoint and add a condition like item.price < 0. Execution only stops when the condition holds. Alternatively, a "log evaluated expression" breakpoint prints a value to the console without stopping—essentially a puts you never have to remember to delete.
Debugging inside Docker or on a remote host
If your app runs in a container, the local one-click setup does not apply. The supported path is remote debugging: the app starts under rdebug-ide, and RubyMine attaches over a socket. A typical invocation inside the container looks like:
bundle exec rdebug-ide --host 0.0.0.0 --port 1234 --dispatcher-port 1235 -- bin/rails server -b 0.0.0.0
Run this where your app normally starts (e.g., as the container command or via docker exec), with the debug gems in the bundle and the ports published. Then create a Ruby remote debug run configuration in RubyMine pointing at the container host and port 1234, and start it. The app will wait for the IDE to attach before booting.
Two things to check if it does not connect: the ports must actually be reachable (a common Docker gotcha is exposing but not publishing them), and the debug gem versions inside the container must be compatible with your Ruby version.
The honest trade-offs
The debugger is not free, and pretending otherwise leads to frustration:
- Version lag.
debaseandruby-debug-idehistorically trail new Ruby releases. On a brand-new Ruby, the bundled debugger may simply fail until support lands. Check Settings/Preferences > Ruby SDK and Gems to see which debug gems RubyMine installed for your interpreter before relying on it. The communitydebuggem or plain puts remains a valid fallback. - Forking servers and preloaders. Puma in clustered mode or the Spring preloader can produce breakpoints that never hit or hit in the wrong process. Disabling Spring (
DISABLE_SPRING=1) or running Puma in single mode while debugging is the standard workaround. - Overkill for one-shot checks. If you need one value from a script that runs in 200 ms, puts is genuinely faster than launching a debug session.
Try it in ten minutes
The fastest way to judge: open any Rails project, set a breakpoint in a controller action, and hit Debug on the default Rails configuration. While paused, run one Evaluate Expression query you would otherwise have added a log line for. If the debugger attaches cleanly on your Ruby version and that one pause saves you a rerun, the case makes itself. Keep puts for throwaway scripts and production logs; reach for the debugger when you do not yet know which variable is lying to you.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.