Debugging Ruby in VS Code
Greetings, friends! In this tutorial, we'll learn how to get the Ruby debugger working in VS Code on macOS! I will go over how to use rbenv
to create a virtual Ruby environment that targets a specific version of Ruby, how to install the Ruby debugger, rdbg
, and how to debug Ruby scripts in VS Code! Let's get started!
Ruby Version for this Tutorial
As of the time of this writing, the latest stable release of Ruby is version 3.1.2. However, I will be using version 3.0.0 for this tutorial. I recommend using version 3.0.0 or higher.
rbenv - The Ruby Version Manager
I will be using rbenv as the Ruby version manager which makes it easy to install and switch between different versions of Ruby. It's a good idea to read how it works and the installation instructions.
For my setup, I chose to use the Homebrew method, since I'm using macOS. If you're using macOS, the following command will install it using Homebrew.
brew install rbenv ruby-build
The ruby-build package is a command-line utility for installing any version of Ruby. The rbenv
package uses ruby-build
to install various versions of Ruby.
To set up rbenv
, we can run the following command in our terminal or shell:
rbenv init
However, it's better to have this command run automatically when we open up a shell, so we don't have to manually start rbenv
every time. Run the following command to add a line to your .zshrc
file (if you're using Zsh) or to your .bash_profile
(if you're using Bash). This line will start the rbenv
program when your shell is opened.
For Zsh shells:
echo 'eval "$(rbenv init -)"' >> ~/.zshrc
For Bash shells:
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
If you need help figuring out what your current shell is, run the following command in your terminal:
echo $0
We can check if rbenv
is working properly by running rbenv
in the terminal. If successful, then we should see a list of useful rbenv commands available to us.
rbenv 1.2.0
Usage: rbenv <command> [<args>]
Some useful rbenv commands are:
commands List all available rbenv commands
local Set or show the local application-specific Ruby version
global Set or show the global Ruby version
shell Set or show the shell-specific Ruby version
install Install a Ruby version using ruby-build
uninstall Uninstall a specific Ruby version
rehash Rehash rbenv shims (run this after installing executables)
version Show the current Ruby version and its origin
versions List installed Ruby versions
which Display the full path to an executable
whence List all Ruby versions that contain the given executable
Installing Ruby 3.0.0 using rbenv
Now that we have rbenv
working correctly, let's install Ruby version 3.0.0 (or any version you prefer that is higher than 3.0.0).
rbenv install 3.0.0
Next, let's create a new directory where we can experiment with Ruby files, gems, and debugging. I will call my directory ruby_debugging
, but feel free to choose whatever name you want. Once we have the debugger working in this directory, we can transfer our knowledge to an actual Rails project. I like to keep things simple and start in a new project when trying to get new tools working.
Once you're inside the ruby_debugging
directory (or whatever you decided to name it), we can run the following command to "set" the local Ruby environment:
rbenv local 3.0.0
This will generate a .ruby-version
file that only contains 3.0.0
. The rbenv
program uses .ruby-version
files to recognize what version of Ruby it should use in a given directory. If we changed this number to 3.0.1
, then rbenv
would think the current Ruby environment is 3.0.1
instead.
If we wanted to create a global Ruby version environment, then we could have run the following command instead:
rbenv global 3.0.0
This would have created a .ruby-version
somewhere in our home directory. The rbenv
program will first look for a .ruby-version
file in our local current directory, and then try to use the global .ruby-version
file instead (if it exists). If a global file doesn't exist, then rbenv
will fall back to our system Ruby version. On macOS, Ruby is installed by default and therefore, the system Ruby version may correspond to that.
I tend to prefer using rbenv local 3.0.0
instead of setting a global version. We can verify version 3.0.0 is being used by running the following command:
ruby -v
You should see something like the following:
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin21]
Installing Bundler
Alright. We've installed Ruby 3.0.0 and configured a Ruby environment to use this version. Now, we need to install Bundler. If you come from a JavaScript and/or Node.js background, then you can think of Bundler as the equivalent npm's dependency resolution. Bundler will install all of the required gems (Ruby packages) you need for a particular project and let you specify exact dependencies.
Let's install bundler using RubyGems which should be installed on your Mac by default.
gem install bundler
We can use the gem which
command to make sure that bundler is installed and accessed using rbenv
:
gem which bundler
You should see bundler installed within a path that includes .rbenv
. An example path would be:
/Users/inspirnathan/.rbenv/versions/3.0.0/lib/ruby/3.0.0/bundler.rb
This means rbenv
is working correctly. It's really important that your shell initializes rbenv
correctly, so that gems are installed with the help of rbenv
. That way, all gems installed using version 3.0.0 will be stored in a separate directory than had we installed gems using, say, version 3.1.2.
We can double-check rbenv
is loaded correctly by checking the $PATH
environment variable on our computer.
echo $PATH
Make sure your $PATH
includes something like $HOME/.rbenv/shims
and $HOME/.rbenv/bin
where $HOME
is expanded as the path to your home directory. This will be important in the next step when we install gems that include executables/binaries.
Installing Gems using a Gemfile
As I mentioned previously, Bundler is a way to install gems and their dependencies, similar to npm for JavaScript or Node.js. Instead of using a package.json
file, we use a file called Gemfile
. Create a new file named Gemfile
and add the following contents:
source 'https://rubygems.org'
gem 'debug'
Next, run the following command:
bundle install
As a shortcut, we can just run bundle
instead of bundle install
. This will install all the gems included in our Gemfile
. In our case, only the debug gem is installed. This gem should download an executable called rdbg
which will be installed as a shim using rbenv
. We can verify that the debug
gem was installed using rbenv
by running the following command:
rbenv which rdbg
You may see a path to the rdbg
executable that includes .rbenv
. This executable is the Ruby debugger!
Creating Binstubs
The Bundler tool has a feature that helps us create binstubs. Binstubs are scripts that wrap around executables such as rdbg
.
Let's create our own binstub for rdbg
using Bundler! Run the following command in your terminal. Make sure you're in the same directory we've been working in throughout this whole tutorial!
bundle binstubs debug
Note that we specified the name of the gem, debug
instead of rdbg
. After running the above command, Bundler will create a new file located at ./bin/rdbg
. Now we can run this executable using a path relative to our current directory!
Testing the Ruby Debugger
Let's make sure the Ruby debugger, rdbg
, is working correctly. Let's create a Ruby script called main.rb
with the following contents:
module SimpleMath
def self.add(x, y)
x + y
end
def self.subtract(x, y)
x - y
end
end
puts SimpleMath.add(1, 2)
puts SimpleMath.subtract(5, 2)
Then, run the following command:
./bin/rdbg main.rb
If successful, the Ruby debugger should be working from within your terminal! If not, then there's likely a problem with your $PATH
environment variable, or maybe rbenv
isn't starting correctly in your current shell. You can type continue
or c
and hit Enter
to navigate through debugger.
Installing and Using the Ruby Debug Extension
Now that we have the Ruby debugger installed, it's time to use it with VS Code! First, let's install the VSCode rdbg Ruby Debugger extension. Next, we need to go back to our current project
and create a new directory called .vscode
. Then, create a new file within the .vscode
directory called launch.json
. VS Code uses a launch.json
file as a way to configure the debugger for one or more programming languages such as Ruby. Insert the following contents into this file:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "rdbg",
"name": "Debug current file with rdbg",
"request": "launch",
"script": "${file}",
"args": [],
"useBundler": false,
"command": "ruby"
},
{
"type": "rdbg",
"name": "Attach with rdbg",
"request": "attach"
}
]
}
Notice how the useBundler
option is set to false
and the command
is set to ruby
. This means that we can expect the Ruby debugger VS Code extension to execute Ruby scripts using something like ruby main.rb
If useBundler
was set to true
, then the Ruby debugger VS Code extension would run Ruby scripts using bundle exec and end up executing Ruby scripts using something like bundle exec ruby main.rb
.
Let's now talk about the different ways of activating the debugger. As you may have noticed in the launch.json
file, there are two configurations we can use when launching the Ruby debugger, rdbg
, with VS Code:
- Launch (named "Debug current file with rdbg")
- Attach (named "Attach with rdbg")
We'll look at the "Launch" configuration first. This configuration lets us run the Ruby debugger with a click of a button from within VS Code.
Navigate to the main.rb
file from within VS Code. Then, on the Primary Side Bar (left side bar) of VS Code, you should see a symbol that looks like a triangle with a bug icon on top. Click on it to navigate to the "Run and Debug" view. Alternatively, you can use the Command + Shift + D
keyboard shortcut to navigate to this view.
Next to "RUN AND DEBUG," you should see a green arrow and a dropdown menu. Select "Debug current file with rdbg" from the dropdown menu and click on the green arrow to start the debugger! If all goes well, you should be able to debug in VS Code! You can add breakpoints anywhere in the code like you normally would with a JavaScript debugger. Just click to the left of a line number in main.rb
to insert a new breakpoint.
As shown above, VS Code provides a "variables" section where we can see the values of each variable inside our program while it is running. We can also hover over variable names or method parameters to see what their current values are.
Pretty cool! It's like we're debugging JavaScript using Chrome DevTools! Actually, the rdbg
debugger can be attached to Chrome, so we can inspect Ruby code from within Chrome DevTools. I'll take about how to do that in a future tutorial 😁.
If you're having trouble getting the "launch" configuration to work properly, I suggest trying the "attach" configuration instead as described in the next section.
The Attach Configuration
In the previous section of this tutorial, we discussed the "launch" configuration, but the rdbg
program also supports the "attach" configuration. This lets us attach a debugger to a program such as VS Code.
Close out the debugger if you haven't already. Then, open up a terminal and run the following command:
./bin/rdbg --open main.rb
Notice that we're using the binstub we created earlier in this tutorial to run the rdbg
executable! After the above command is run, you should see something similar to the following:
DEBUGGER: Debugger can attach via UNIX domain socket...
Next, go back to VS Code, navigate to the "Run and Debug" view, and click on the dropdown near the green triangle. This time, we'll choose the "Attach with rdbg" option. Click the green triangle, and then VS Code will attach to our rdbg
program that is currently running.
If successful, you'll be able to debug Ruby scripts in VS Code similar to the "launch" configuration!
Conclusion
You now have the power to debug Ruby code using VS Code 💎! Your power as a developer has increased tenfold! You can now insert breakpoints, inspect variables, look at the call stack, and more! Happy coding 🙂