If you use VS Code for Ruby development or you’re looking for ways to improve your debugging workflow in your Rails app, you’ll want to check out this screencast.
There have been a ton of quality of life improvements to Ruby over the past few years, and one of the headline features of last year’s 3.1 release was a dramatic overhaul of the long-neglected lib/debug.rb
as a new gem debug.gem, led by Koichi Sasada.
Aside from providing a more pleasant, feature-rich command line interface with many features you know and love from pry and Byebug, the new debug gem can also communicate with GUI front-ends like VS Code. This allows you to attach VS Code’s IDE-like debugger interface to any running Ruby process without interfering with your shell’s STDIN and STDOUT.
This is made all the more important if you’re starting a new app in Rails 7 and using esbuild, because Rails will generate a new bin/dev
script that combines your Rails server, CSS watcher, and JS bundler into a single process managed by foreman. This is really convenient for running your Rails app with a single command, but it’s a nightmare for trying to interrogate the system with an interactive debugger.
But it turns out, it’s not a nightmare! It’s actually pretty awesome!
As this screencast shows, you can have the best of all worlds if you launch your Rails process with the new debug gem’s rdbg
command and then attach a remote debugger separately. Not only do you get a single command to run your collective development servers, you can easily set breakpoints and catch them right in your editor!
A couple other resources mentioned in the screencast:
- Koichi’s awesome rdbg extension for VS Code
- The launch configs extension for running a custom launch config with a key binding
[00:00] (upbeat tune playing)
[00:03] - I am starting to play with VS Code, Visual Studio Code
[00:06] and getting my Ruby on Rails configuration
[00:09] just how I like it.
[00:10] And one thing that I came across was that
[00:12] the debugging story has gotten very, very, very, very good
[00:16] in Ruby 3.1, where up until this point
[00:19] it actually lagged behind most other languages,
[00:22] of course being super dynamic interpreted language,
[00:24] you would expect that to a certain extent,
[00:28] but Koichi Sasada in particular,
[00:30] has really put in a lot of work
[00:31] to modernize the Ruby builtin debugging facilities
[00:36] as well as a gem called debug
[00:39] that will allow us to actually use
[00:41] the native VS Code debugger to debug our stuff.
[00:45] And I just discovered this yesterday
[00:46] and I realize a lot of people probably don't know this yet.
[00:48] So I'm going to walk through a little bit
[00:50] of like the Rails, so using Ruby on Rails 7.
[00:53] A little bit about like why debugging has gotten trickier
[00:56] even though it's gotten more advanced for Rails 7,
[01:02] as well as how to wire everything up to VS Code.
[01:05] So you can use like a nice vanilla out of the box
[01:07] Rails 7 installation, and also take advantage
[01:10] of the new fancy debugging facilities
[01:13] that are much more native feeling in something
[01:15] like VS Code and, and surely other, you know, kind of
[01:18] like more batteries included editors like RubyMine.
[01:21] All right, So we're gonna start
[01:23] with just making a new application, I suppose.
[01:29] So let's go into a code directory
[01:31] and I'm gonna make a new,
[01:35] well, first of all let's check our versions, right,
[01:36] because time keeps moving.
[01:41] I'm not great at typing.
[01:42] Okay. So we're on Ruby 3.1.2 Rails 7.0.3.1.
[01:46] We're gonna make a Rails new app.
[01:48] We're gonna call it rebug.
[01:50] And this time we're gonna use esbuild as opposed
[01:53] to Webpacker, or as opposed to, you know, Sprockets
[01:57] for a JavaScript concatenation.
[01:59] And we're also gonna set it up to say
[02:00] hey we wanna use tailwind for CSS.
[02:04] We're not gonna actually write any tailwind today
[02:05] but I wanna create an environment and then,
[02:08] oh yeah and Propshaft as opposed to Sprocket.
[02:10] So, so Propshaft is a more modern, more live,
[02:14] simple
[02:16] asset
[02:18] pipeline
[02:19] Asset manager to like do the stuff
[02:21] with the application dot CSS and, and
[02:23] application dot JS without necessarily like considering
[02:28] all the stuff Sprockets had to do with like stuff
[02:31] like actively transpiling CoffeeScript and SAS.
[02:34] So it's just a simpler one.
[02:36] When we do a new application this way
[02:39] it's actually going to, by default,
[02:41] create a procfile dot dev file.
[02:44] And when it creates that procfile dot dev,
[02:46] that is like going to be actually invoked whenever
[02:49] we say bin slash dev.
[02:51] So you might be familiar with over the last many years,
[02:54] running your Rails app in one terminal window,
[02:56] and then your Webpacker in another one,
[02:59] because it's kind of gotten outta hand
[03:01] You can, you can see the procfile dot dev
[03:05] pop into rebug,
[03:08] You can take a look at it.
[03:09] So here we have now a web process, a JavaScript process,
[03:13] and a CSS process, cause we're using JS bundling,
[03:15] CSS bundling, and then of course
[03:18] you had to run your Rails server.
[03:19] And so that's just like, keeping three terminals open
[03:20] is too much and you can
[03:22] of course you can still go and run Rails s
[03:25] and you can you can run yarn build and so forth,
[03:28] but you can also run this new bin dev
[03:31] that's built in for you as a bin stub.
[03:33] And so it'll just
[03:35] All it does is make sure that that foreman is installed
[03:38] and then it'll start foreman, nothing fancy.
[03:40] So if we run bin dev like that
[03:41] you'll get some color coding
[03:42] and it'll run all three of these things at once.
[03:45] All right.
[03:46] So that's our application, I suppose if I just run it now
[03:50] and I let's say open up,
[03:58] Neat. All right,
[03:58] So I got a Rails thing.
[04:02] I'm running my Rails
[04:03] and now I'm gonna start writing some code.
[04:05] And like I said, I'm getting into VS Code.
[04:07] So we're gonna open up VS Code here.
[04:10] All right, so I'm in VS Code,
[04:11] everything is new again, nothing's committed.
[04:13] So we're just gonna ignore the commit status
[04:15] for everything today.
[04:16] And we're gonna make just a couple adjustments.
[04:19] We're gonna start writing some code here.
[04:21] So I'm gonna get rid of the default Rails stuff.
[04:24] And I'm gonna say
[04:25] we're gonna cover resources called things.
[04:27] And the root of our application is gonna be things index
[04:31] which is gonna necessitate a things controller
[04:35] which we can take a look over here.
[04:38] We can say, Hey, all right,
[04:39] So things controller dot RB,
[04:42] boom.
[04:43] And then, I'm still getting used to the VIM emulation
[04:47] inside of this thing, it's pretty good. Fine.
[04:51] I don't love it, but all the other kind of perks
[04:54] are enough to keep me interested.
[04:57] All right.
[04:58] Let's see.
[04:59] Okay. So now I've got a things controller
[05:03] things index is my root,
[05:05] and I just wanna render
[05:08] some JSON.
[05:09] So let's say like
[05:12] now
[05:14] and actually let's make a variable
[05:16] so we have something to debug.
[05:18] So now equals time dot zone dot new
[05:25] or time dot zone dot now?
[05:27] I always forget. Yeah. time dot zone dot now.
[05:28] Okay, so then we're gonna render some JSON now got it.
[05:31] And the standard of course
[05:32] is gonna remind me to remove the white space.
[05:35] All right.
[05:36] So if I look at local host 3000, now I get my,
[05:41] you can see it up in the corner, the JSON.
[05:43] All right, neat.
[05:44] So let's say that I am in the, you know
[05:49] I want a debugger.
[05:50] Now you might be familiar with pry or byebug,
[05:53] in Rails 7 They changed the default,
[05:55] and maybe this is starting with 3.1,
[05:57] It would make sense if it was,
[05:58] they changed the default to actually debug here.
[06:00] So you can see there's a whole guide
[06:02] on the changes to debugging.
[06:04] Here's the debug gem and which platforms it supports.
[06:07] And so we're gonna have a different set of commands now,
[06:11] you can still use, Binding, IRB and other stuff,
[06:14] but we're gonna just say
[06:15] debugger here to put in a debug point,
[06:17] and then gonna take a look at our terminal.
[06:21] Now, if I refresh the page at this point,
[06:23] I do indeed catch the debugger.
[06:24] You can see the debugger here,
[06:26] you can see the nice, like, where am I?
[06:28] But like foreman and foreman-like stuff like
[06:31] I imagine Hivemind, Overmind and similar
[06:34] process splitting, aggregating tools
[06:36] for running multiple processes in one shell is not designed
[06:40] for like capturing standard in particularly gracefully.
[06:42] So if I wanna type like, okay, so now,
[06:45] oh well that doesn't do what I want, really.
[06:47] And if I say C and I run, will it actually continue?
[06:52] Like maybe not continue command completed, okay, cool.
[06:57] And so it's just like not a particularly graceful way
[07:00] to be like, all right.
[07:00] So LS now.
[07:04] And it didn't actually,
[07:05] it's like list command.
[07:06] It's clearly not picking up all
[07:08] of my keystrokes in order, which is to be expected.
[07:11] I mean, that's not,
[07:13] this is not like the most delightful debugging experience.
[07:16] So of course like what I could do
[07:19] if I killed everything,
[07:22] Okay, cool.
[07:23] So what I could do of course is I could just like
[07:25] go back to the, the good old days of running bin Rails S
[07:28] and I could, let's say load this page again.
[07:32] Now I get my debugger and I can, you know, if you've
[07:34] if you used pry, there's a new outline command
[07:38] that'll give you all of the methods
[07:40] just like pry's LS command, which is really
[07:43] like my favorite reason for using pry's to figure out
[07:45] you know, like what are the methods on this thing?
[07:48] And LS is indeed
[07:51] an alias of outline for people used to that,
[07:53] but it just like RDBG
[07:58] allows you to do like, you know
[08:01] break point debugger commands, like continue
[08:05] and c, even a queue for quit.
[08:08] You can get these little things, like step over or step.
[08:13] I don't know if step in or step over is a default for step.
[08:16] So that's like, neat.
[08:18] You can, you can do all that stuff.
[08:19] You can look at variables.
[08:20] You can of course, like ask questions, like now dot day
[08:23] that's just like the traditional debugging facility.
[08:25] But of course we lose the procfile running everything
[08:28] for us, and we'd be going back to three.
[08:29] So like, what I wanna avoid, right,
[08:31] Whenever I'm programming is I wanna get fast feedback
[08:33] and I wanna have a quick way to hook
[08:35] in to a particular thing without necessarily
[08:37] going over to another terminal and
[08:39] killing all my servers and then starting all over again
[08:41] and then trying to get everything into the same state.
[08:43] So what I want instead is to make it really easy
[08:45] just attach and detach a debugger
[08:46] from whatever my process is, and I wanna run the process
[08:48] in the most convenient way possible.
[08:50] So here we go.
[08:50] So we're gonna exit.
[08:53] alright, we quit our server.
[08:55] We're we're not gonna run things that way anymore.
[08:58] And instead,
[09:00] what we can do is we can think
[09:02] about this new tool called RDBG
[09:05] which comes along with the debug gem,
[09:09] which Koichi has written, which is sort of inheriting
[09:12] the old lib debug from Ruby.
[09:17] And we can actually say, let's see RDBG
[09:19] and you can just give it tac c
[09:21] and then you can give it a command
[09:25] and what it'll do is it'll actually
[09:27] launch whatever your command is
[09:29] but first pause and attach to the debugger to it.
[09:33] And we can just say, continue here.
[09:34] And it's just running our service, we can refresh the page,
[09:37] we catch the thing.
[09:38] This is not that dissimilar, of course
[09:40] from just like writing debugger in there.
[09:42] But what's interesting is like
[09:43] if you learn this command a bit,
[09:47] you can see how
[09:49] you can start to build up what you might be interested in.
[09:51] So we can say like tac end for example
[09:54] and it'll run nonstop.
[09:57] It'll just run for us
[10:00] it won't do that initial pause.
[10:02] Great.
[10:05] Okay. Quit.
[10:06] Okay. And then you can also tell it to open a particular
[10:12] front end for the debugger.
[10:14] And now by default, that front end is RDBG.
[10:16] That's like what we've been doing, now, instead
[10:19] what we're gonna do is we're gonna actually like launch this
[10:21] with VS Code.
[10:24] We're just gonna take a couple steps because by default,
[10:26] it's not gonna just magically work
[10:31] because we don't have any sort of like awareness of
[10:34] the Ruby debugger in VS Code.
[10:36] So what we're gonna do is we're gonna go over
[10:38] to the extensions world, We're gonna say
[10:41] search for VS Code RDBG and hope that we find it.
[10:46] There we go.
[10:47] So Koichi also wrote this VS Code RDBG Ruby debugger,
[10:51] We're gonna install it here.
[10:56] And because I don't trust VS Code just yet,
[10:58] even though it's all JavaScript and cool,
[11:00] I am going to quit out entirely.
[11:04] Quit that too.
[11:07] And I'm gonna run
[11:09] this again.
[11:10] It's going to attach VS Code here
[11:12] I can see I'm attached now to a VS Code debugger,
[11:19] and if I refresh the page, I'm here, I'm in the debugger.
[11:22] Now I'm again, I'm in an arbitrary window though.
[11:24] I'm not in like my application window right now
[11:27] but I can see like, okay, cool.
[11:28] I have, now I've got like a step a debugger.
[11:31] These commands up here I can like
[11:32] step over a line and so forth.
[11:34] I also have this debug console in addition to my terminal.
[11:36] So I could run the terminal here,
[11:37] but you can see I'm like in this variable
[11:40] like folders like this Tempter.
[11:41] So it's not quite perfect, but I can get in like this.
[11:44] Now, if I wanted to fold this into my application
[11:49] what I could do instead is I could actually wire this
[11:52] in as a launch configuration
[11:55] to attach manually to a running process.
[11:58] So let's try doing that, it's a little bit finicky,
[12:00] I haven't figured out the exact order of what to do
[12:03] with this stuff, but we're gonna quit out again.
[12:07] I'm gonna go here, we've exited that,
[12:10] I'm going to run code period
[12:11] to open up Visual Studio Code to a particular place.
[12:14] I've got this extension now down here, the VS Code debugger,
[12:18] I'm going to now make a launch config.
[12:23] I'm gonna do this.
[12:24] Now you can see it's got two different launch configs
[12:27] by default, the debugger, the current file with RDBG.
[12:30] I'm not gonna use that one today,
[12:31] That's actually more self explanatory,
[12:35] but I do wanna be able to attach with RDBG.
[12:39] And so you can see this right here.
[12:43] And so what I'm gonna do
[12:46] is I'm going to just
[12:47] from the terminal window here,
[12:51] sticking in the terminal.
[12:54] I'm going to run
[12:58] that same command that we just had over in my terminal.
[13:01] I'm starting to get at more and more stuff into
[13:03] VS Code in the interest of living the, the sweet IDE life.
[13:09] Okay. So I'm gonna run that here.
[13:14] Now I'm running and I can run attached with RDBG.
[13:20] Okay. Now I refresh the page
[13:22] and I am inside now, my actual like, you know project.
[13:27] So if I'm looking at the Explorer view, it's synced up
[13:29] with where I am in my source code.
[13:31] And I'm right at that debugger
[13:32] and I can still type into my debugger console.
[13:35] So that's pretty slick.
[13:36] I can hit play and I can continue.
[13:38] In fact, I can remove the debugger now
[13:40] that I'm like, sort of attached to a session
[13:45] or I know how to attach to a session, I can actually
[13:48] create a break point here by just making
[13:51] a little red dot here in the gutter.
[13:53] And now when I visit the page,
[13:58] it should have caught that,
[14:02] it didn't catch that.
[14:04] Okay. So why isn't the debugger going?
[14:08] Did I, no, I got a break point.
[14:12] What did I do wrong?
[14:21] Restart.
[14:27] That probably was the wrong thing to do.
[14:29] Did it quit? No.
[14:31] Cannot find attachable Ruby project.
[14:32] Okay. So I'm gonna try that one more time.
[14:35] That's surprising.
[14:36] It's worked every time until I do it on video, naturally.
[14:40] Ah, shoot.
[14:44] Oh yeah, I didn't actually attach I that time.
[14:47] Okay. So I'm attached now.
[14:49] I got my debugger.
[14:51] I refresh, cool. And I'm here.
[14:54] And if I refresh again, it should reattach. Okay. So
[14:59] it works usually,
[15:01] don't feel bad if things don't work the first time for you,
[15:04] the biggest, most fun part of being a programmer
[15:07] is the banging your head up against a wall
[15:08] and then getting different results
[15:09] each time you try something.
[15:12] All right.
[15:13] So now I can debug whenever
[15:16] and I create little break points.
[15:19] I can enable disable the break points
[15:21] this tiny little view down here, itty-bitty guy.
[15:26] I'll see the break point there
[15:27] and I can enable disable it from a central place.
[15:31] Of course, you know, like if you're familiar
[15:32] with step debuggers, all of the step commands are up here
[15:36] and you can also kind of expand here
[15:37] and look in at whatever your state is.
[15:39] There's nothing interesting here
[15:40] cause I'm just like on the top level a controller
[15:42] and I don't have any of my own code,
[15:43] but if you're in like a class
[15:44] and you had like, you know, state
[15:45] or you had other things going on
[15:46] or you had a big call stack
[15:48] you'd have interesting stuff to look at.
[15:50] And of course the call stack here
[15:51] is split up because Puma by default has a gajillion servers.
[15:56] Okay. So we've got, now we've made a lot of progress.
[15:58] We've got a new Rails application.
[16:00] It's using
[16:02] this new debug gem.
[16:04] We've got the debug gem looking at VS Code
[16:07] and we're almost where we wanna be
[16:09] except we're still just running bin Rails S
[16:11] to get a debugger, which is not quite where we wanna be.
[16:15] We wanna be running that new bin dev command
[16:17] which is gonna run foreman.
[16:18] So that's gonna require us to go
[16:21] and take a look at procfile dot dev.
[16:25] And here we can actually just put exactly what we had
[16:28] from our terminal using
[16:30] RDGB tac n
[16:33] tac tac open vscode
[16:37] and then leave that remaining tac tac
[16:41] and a space
[16:42] so that the command that runs is this right here.
[16:45] Now this is like a generated file, but it lives in the
[16:48] in the root of the project,
[16:49] and so we can safely edit it
[16:51] and just know that if like we do rerun, a Rails generator
[16:55] we might have to like, you know
[16:56] deal with this in version history.
[16:57] But like, this is ours, we can do this if we want.
[17:00] Now it says VS Code here,
[17:03] I can show you a trick later to like
[17:05] extract that away into an environment variable
[17:07] because maybe not everyone on your project uses
[17:08] VS Code and still wants to run bin dev, but let's go ahead
[17:11] and try running bin dev here in our terminal.
[17:14] So now we're starting three different processes up
[17:17] now with this debugger, like I just accidentally showed you
[17:21] if you refresh the page, it won't catch
[17:23] by default because we've not attached a debugger to it.
[17:26] But with this break point,
[17:30] we can, you know, launch and there's a way to launch control
[17:33] I think is the name of the extension here. Launch.
[17:39] Launch configs.
[17:41] This, this little relatively unknown extension
[17:43] will allow you to make keyboard shortcuts
[17:46] for launch configs and so I would probably
[17:48] just bind this to something like command shift,
[17:53] alt R or D or something.
[17:55] But anyway, I'm gonna run this now,
[17:58] when I run it, it's gonna be running, I refresh
[18:02] and now I've caught my debugger
[18:03] and you can see I've done that
[18:04] without goofing up all of the output in the main foreman.
[18:09] In fact, I hit play, you can see the debugger got connected,
[18:12] but then it just finished the request
[18:13] standard in standard out didn't get all goofy
[18:15] and wonky in the process.
[18:17] So that was pretty cool.
[18:19] We got through a lot, the little trick that I use
[18:23] for avoiding having like any editor specific stuff
[18:28] in my procfile is you can use an environment variable
[18:33] instead, you can do like an expression here
[18:35] because this is ultimately just gonna be processed
[18:37] by your shell.
[18:39] You can say RDBG, This is a custom variable that you can own
[18:43] that you can create, and then you can set a default for it.
[18:46] So maybe you just make it RDBG by default,
[18:49] which of course as we've shown, is not particularly
[18:51] user friendly when you're running via foreman,
[18:53] but it's the most obvious thing.
[18:55] And so now when we run it, I've actually already set
[18:59] that variable in my profile as a user.
[19:02] So if I like, you know, echo this out
[19:03] you can see it says VS Code.
[19:05] And now I can run bin dev, when I run bin dev.
[19:08] And this is a second verse saying, this is the first kind
[19:12] of thing I've attached
[19:14] with RDBG,
[19:16] I'll get used to that eventually
[19:18] I'll refresh this page
[19:19] and then you can see I'm caught again.
[19:21] So that way I can when time comes to commit all this to git
[19:25] I don't have to have VS Code,
[19:26] the word VS Code or any one particular tool anywhere
[19:29] in my, you know, git repository, it can all be agnostic.
[19:32] And yeah, so like this is pretty darn slick,
[19:35] in my opinion, like, you know it gets you that kinda like,
[19:38] you know, neat introspective
[19:41] IDE feel without any of the gross like lock in
[19:45] and proprietary-ness
[19:47] or slowness, it's actually like really fast.
[19:49] So kudos to Koichi and the rest of the Ruby core
[19:53] and Ruby committers team.
[19:55] Ruby 3.1 is just like Ruby 3.0 before it, it keeps coming
[19:59] with like more and more quality of life stuff,
[20:01] and so if you haven't upgraded a 3.0
[20:04] or to 3.1, when you do I strongly encourage you to check out
[20:08] a lot of the talks from the last couple years of RubyKaigi,
[20:11] RubyKaigi takeout, they they've done
[20:13] lots of great videos in English
[20:15] about all of the improvements that they've made to the
[20:21] IRB, to the
[20:24] read line, input output stuff like to
[20:25] make the interactive shell really useful,
[20:27] and now this RDBG stuff supporting any arbitrary
[20:30] debugger front end is this really slick.
[20:31] So thanks again to our friends
[20:34] on the Ruby core team and, and the other Ruby
[20:36] committers who who've made this just so great.
[20:40] So I'm really excited to like, you know
[20:41] start developing Rails this way and having like a
[20:44] a nicer debugger story than I've had before.
[20:46] Cause it ultimately just means faster feedback
[20:48] and getting answers to the questions
[20:50] that I have for my computer faster.
[20:51] So check it out and I hope you have a great time.
[20:54] Thanks for watching.
Your monthly dose of better dev
The Test Double Dispatch: hot takes, smart tools, and coding shortcuts to make your life easier—delivered every month.