Debugging Ruby in VS Code

Published: Wednesday, August 24, 2022

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.

text
Copied! ⭐️
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:

text
Copied! ⭐️
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:

text
Copied! ⭐️
echo 'eval "$(rbenv init -)"' >> ~/.zshrc

For Bash shells:

text
Copied! ⭐️
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

If you need help figuring out what your current shell is, run the following command in your terminal:

text
Copied! ⭐️
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.

text
Copied! ⭐️
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).

text
Copied! ⭐️
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:

text
Copied! ⭐️
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:

text
Copied! ⭐️
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:

text
Copied! ⭐️
ruby -v

You should see something like the following:

text
Copied! ⭐️
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.

text
Copied! ⭐️
gem install bundler

We can use the gem which command to make sure that bundler is installed and accessed using rbenv:

text
Copied! ⭐️
gem which bundler

You should see bundler installed within a path that includes .rbenv. An example path would be:

text
Copied! ⭐️
/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.

text
Copied! ⭐️
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:

ruby
Copied! ⭐️
source 'https://rubygems.org'
gem 'debug'

Next, run the following command:

text
Copied! ⭐️
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:

text
Copied! ⭐️
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!

text
Copied! ⭐️
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:

ruby
Copied! ⭐️
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:

text
Copied! ⭐️
./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:

json
Copied! ⭐️
{
  // 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:

  1. Launch (named "Debug current file with rdbg")
  2. 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.

The VS Code debugger in action. Two breakpoints are inserted, one to the left of the line number 3 and one to the left of line number 7. Then, the green arrow next to run and debug is clicked.

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:

text
Copied! ⭐️
./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:

text
Copied! ⭐️
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 🙂

Resources