Debugging Ruby on Rails in VS Code

Published: Wednesday, September 14, 2022

Greetings, friends! In the previous tutorial we learned how to get the Ruby debugger working in VS Code by installing the rdbg gem and a VS Code extension called VSCode rdbg Ruby Debugger by Koichi Sasada. In this tutorial, we'll use the same tools to debug a Ruby on Rails application on macOS. Please make sure to read my previous tutorial before reading this one!

Create a New Directory

For this tutorial, I suggest making a new directory to test the debugger with a new Rails application. I will create a new directory called rails_debugging. Then, I will open VS Code with this directory as the root of my project. Make sure you do this if you're following along! It'll be important later!

Once you're more familiar with the debugger, you can apply your newfound knowledge to an existing Rails application. Just make sure that your Ruby and Rails versions are high enough that they are supported by the debug Ruby gem.

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, similar to my last tutorial. I recommend using version 3.0.0 or higher.

Within the rails_debugging directory we created earlier, run the following command:

text
Copied! ⭐️
rbenv local 3.0.0

This will create a file called .ruby-version in the current directory.

File structure in VS Code. The current project is rails_debugging and contains only one file named .ruby-version.

Installing Ruby on Rails

For Ruby on Rails, the latest stable release is 7.0.3.1, released on July 12, 2022. We will use this version for this tutorial, and we'll install it using rbenv.

Create a new file called Gemfile with the following contents:

ruby
Copied! ⭐️
source 'https://rubygems.org'
gem 'rails', '7.0.3.1'

Next, we can install Rails using Bundler. Run the following command:

text
Copied! ⭐️
bundle

This should install Rails using rbenv. We can verify that it was installed in the correct path by running the following command:

text
Copied! ⭐️
rbenv which rails

You should see a path similar to the following:

text
Copied! ⭐️
/Users/inspirnathan/.rbenv/versions/3.0.0/bin/rails

Notice how the 3.0.0 refers to the version of Ruby we're using. This helps makes sure gems installed using a particular version of Ruby is stored in the same path.

We can make sure Rails is working correctly by running the following command:

text
Copied! ⭐️
bundle exec rails -v

Running the above command should output Rails 7.0.3.1, since that's the version we specified in our Gemfile.

Next, let's create a new Rails application.

Bootstrapping a new Rails App

We can create a new app using the following command:

text
Copied! ⭐️
bundle exec rails new blog

This will create a new directory called blog that contains a brand new Rails application! Notice that within the blog directory, there's another .ruby-version file. It should contain 3.0.0, since that's the version we used to install the Rails gem in the rails_debugging directory.

Our directory structure should currently look like following:

File structure in VS Code. The current project is rails_debugging and contains one directory called blog and three files: .ruby-version, Gemfile, and Gemfile.lock.

Running the Rails Server

Navigate to the newly created Rails directory using cd blog.

tip
For the rest of the tutorial, it's important to check whether you're currently in the rails_debugging directory, or the blog directory if you're having issues running certain commands.

We can start our new Rails application in multiple ways. Pick your favorite!

  1. bundle exec rails server
  2. bundle exec rails s
  3. bin/rails server
  4. bin/rails s
  5. rails server
  6. rails s

Once the app is started, you should be able to access the app at http://localhost:3000. You can gracefully shut down the server using Ctrl + C in the terminal.

Your server should gracefully shut down, but you may occasionally run into "A server is already running" error. If this happens, run the following command to get list of processes running on port 3000:

text
Copied! ⭐️
lsof -i:3000

Then, look for a row that has "ruby" in the "COMMAND" column. Copy the number in the corresponding "PID" column and then use that number in the following command:

text
Copied! ⭐️
kill -9 PID_NUMBER

Where PID_NUMBER is replaced with the number you found using the lsof -i:3000 command.

Creating a New Controller and View

Currently, our Rails application is new, and there aren't many great places to showcase the power of Ruby debugging within it. Let's create a new controller and view using the following command:

text
Copied! ⭐️
rails generate controller Articles index

This command will cause Rails to create multiple files that adhere to its usual Model-View-Controller (MVC) architecture. You might see the following output in your terminal.

text
Copied! ⭐️
create  app/controllers/articles_controller.rb
       route  get 'articles/index'
      invoke  erb
      create    app/views/articles
      create    app/views/articles/index.html.erb
      invoke  test_unit
      create    test/controllers/articles_controller_test.rb
      invoke  helper
      create    app/helpers/articles_helper.rb
      invoke    test_unit

The above command created a new file called articles_controller.rb. Replace the contents of this file with the following:

ruby
Copied! ⭐️
class ArticlesController < ApplicationController
  def index
    @article = { title: "Best Food", body: "Pizza, donuts, potatoes" }
  end
end

This will create an instance variable called @article that we can pass to the corresponding Article view. Let's open up this view and use this variable.

Open up the file located at app/views/articles/index.html.erb and replace everything with the following contents:

html
Copied! ⭐️
<h1>Articles#index</h1>
<p>Find me in app/views/articles/index.html.erb</p>

<h2><%= @article[:title] %></h2>
<p><%= @article[:body] %></p>

Let's run the Rails server to make sure this page is working! Run rails s to start the server. Then, navigate to http://localhost:3000/articles/index, and you should see the following:

Articles#index Find me in app/views/articles/index.html.erb Best Food Pizza, donuts, potatoes

Debugging Time

It's time to debug the Rails application! Unlike the last tutorial, we don't have to manually install the debug gem or create a binstub. Rails already installs and uses the debug gem. If you look inside the Gemfile within the blog directory, you should see the following somewhere in the file:

ruby
Copied! ⭐️
group :development, :test do
  # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
  gem "debug", platforms: %i[ mri mingw x64_mingw ]
end

By default, the Rails app will use development mode when we start the Rails server using rails s. Therefore, the debug gem will automatically be made available to us!

Remember, installing the debug gem will naturally install the rdbg binary/executable. The rdbg executable is the actual Ruby debugger.

There are multiple ways to debug our new Rails app using rdbg. Here are four methods I found the most useful.

  1. Start the Rails server in development mode (the default mode)
  2. Launch rdbg using VS Code's "launch" configuration
  3. Attach rdbg to a "debuggee" such as VS Code using the "attach" configuration
  4. Run rdbg with the open=vscode option

Let's take a look at each of these four methods.

Method 1 of Debugging with rdbg

Let's look at example of each of the four methods for debugging with rdbg. For the first method, we need to add a breakpoint somewhere in our code. Let's open the articles_controller.rb file and insert a debugger statement. We should end up with the following:

ruby
Copied! ⭐️
class ArticlesController < ApplicationController
  def index
    debugger
    @article = { title: "Best Food", body: "Pizza, donuts, potatoes" }
  end
end

Next, start the server using rails s and navigate to http://localhost:3000/articles/index. The page likely won't load because the debugger has already hit the breakpoint in articles_controller.rb. Switch back to your terminal, and you should see that debugger has already started.

Screenshot of the rdgb debugger paused at a breakpoint on line 3 of the articles_controller.rb file.

You can now look at all kinds of variables here. You can find a list of instructions you can perform in the debugger on the official GitHub page for the debug gem.

You can use c to continue and quit to terminate the debugger (which will likely stop the Rails server as well).

Method 2 of Debugging with rdbg

The next method we will look at involves using the "launch" configuration of VS Code to debug a Rails application using rdgb together with the VSCode rdbg Ruby Debugger we installed in my previous tutorial.

This next step is important. If you've been following along this tutorial, you likely have been using VS Code with the rails_debugging directory opened as the root directory of your project. You can change things later, but I'll be using paths relative to this directory.

Create a .vscode directory in the root of your project, which should be in the rails_debugging directory. Then, create a launch.json file within that directory. Your project structure should now look like the following:

File structure in VS Code. The current project is rails_debugging and contains two directories: .vscode and blog. Inside the .vscode directory is a file called launch.json.

Within the launch.json file, insert the following contents:

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": "./blog/bin/rails s",
      "useBundler": false,
      "command": "ruby"
    },
    {
      "type": "rdbg",
      "name": "Attach with rdbg",
      "request": "attach"
    }
  ]
}

Let me explain what this file is doing. As you're probably aware, the launch.json file is a special configuration file that VS Code uses when activating the built-in debugger. The debugger's user interface is generic and meant to be used with multiple programming languages.

The author of the VSCode rdbg Ruby Debugger extension already hooked into the VS Code API that deals with interfacing with VS Code's debugger and the rdgb debugger. Therefore, breakpoints inserted into VS Code can then be picked up by rdgb.

In "Method 1 of Debugging with rdbg", we were using the debugger statement, but it'd be nice to insert breakpoints using VS Code.

In the launch.json file, notice there is a script property and a command property. When we run the VS Code debugger, it will try to execute the rdbg debugger with a particular command. This command will be of the form, command_property script_property, which equates to ruby ./blog/bin/rails s if we use the launch.json configuration above.

Keep in mind that you can change the script property to be whatever path you need for your setup. Just make sure you're running the debugger from within the rails_debugging directory, since we're navigating to ./blog in the script property.

This will all make more sense when we run the VS Code debugger, and you look at the command ran in your terminal.

First, let's remove the debugger statement from articles_controller.rb and add a few lines that we'll debug with VS Code later.

ruby
Copied! ⭐️
class ArticlesController < ApplicationController
  def index
    title = "Best Food"
    body = "Pizza, donuts, potatoes"
    @article = { title: title, body: body }
  end
end

Next, let's insert a breakpoint by clicking to the left of the line numbers next to title and body.

Screenshot of VS Code opened at the articles_controller.rb file. There is a red dot to the left of line number 3 and line number 4. These red dots signify breakpoints in VS Code.

Finally, let's run the debugger! In your terminal, make sure you're in the rails_debugging directory and not the blog directory. Then, navigate to the "Run and Debug" pane in VS Code using Shift + Command + D or by clicking on the "triangle with bug" icon on the left side of VS Code.

Then, select "Debug current file with rdbg" from the dropdown to the left of the green triangle. Click on the green triangle to run the VS Code debugger.

Run and Debug pane of VS Code. There is a green triangle used to run the debugger. To the right of the green triangle is a dropdown menu. Debug current file with rdbg is the currently selected option.

The rails server should start automatically with the rdbg program attached to VS Code. Navigate to http://localhost:3000/articles/index, and VS Code should automatically take over. You can now debug the Rails application using VS Code!!! 🎉

Screenshot of VS Code opened at the articles_controller.rb file. There is a red dot to the left of line number 3 and line number 4. These red dots signify breakpoints in VS Code. Line 3 is highlighted in yellow.

We can even add additional breakpoints on the fly. In the image below, I added an extra breakpoint to the left of @article.

Screenshot of VS Code opened at the articles_controller.rb file. There is a red dot to the left of line numbers 3, 4, and 5. These red dots signify breakpoints in VS Code. Line 5 is highlighted in yellow.

We can even inspect local variables on the left side of VS Code to see what their values are after iterating through a few breakpoints.

Local variables in the Variables section in VS Code. There are three local variables shown: %self, title, and body.

We can use the buttons at the top of VS Code to step to the next breakpoint, step inside a function, or use the red square button to close down the debugger and Rails server entirely.

Method 3 of Debugging with rdbg

The third method I'd like to talk about involves the "attach" configuration in VS Code. We can attach rdbg to a "debuggee" such as VS Code, Google Chrome DevTools, and even an application running on a Docker container.

In this tutorial, I'll discuss how to attach rdbg to VS Code. Make sure you have closed down the debugger and Rails server from the last step. Currently, you're probably in the rails_debugging directory from the previous step. Navigate back to the blog directory, so we get access to the Rails binstubs and run the following command:

text
Copied! ⭐️
rdbg -n --open -c -- bin/rails s

This will run the Rails server as a "debuggee." Notice how I'm running the rdbg program with a few options.

The -n option will make sure that the the debugger doesn't immediately start at the beginning of a script (which likely would have been at the start of the /bin/rails.rb file).

The --open option will start remote debugging and open a network port. By default, it'll use open a Unix domain socket (UDS).

The -c option lets us run a command. In this case, it's bin/rails s. Therefore, both the Ruby debugger, rdgb, will start, and the Rails server will start.

With the debugger now listening over a Unix socket, let's now attach VS Code to it using the "attach" configuration. Open up the "Run and Debug" pane using Shift + Command + D. Then, select the "Attach with rdbg" option from the dropdown menu to the right of the green triangle. Click on the green triangle to start the debug process, and VS Code will then be attached to the rdbg process.

Navigate to http://localhost:3000/articles/index, and you should be switched automatically to VS Code. You can now debug and insert breakpoints using VS Code again! The "attach" configuration is much easier to get working, in my opinion, than the "launch" configuration, but they each have their own sets of advantages. The "launch" configuration, when launch.json is setup correctly, is faster, but more complicated.

Method 4 of Debugging with rdbg

Instead of having to manually run the debugger within VS Code, the rdbg tool provides another way to start up VS Code with the debugger attached. We can pass vscode to the --open option, so that we can automatically open VS Code's debugger using the "attach" configuration.

Close out of the Rails server and debugger if you have them currently open. Then, let's go back and add a debugger statement inside articles_controller.rb:

ruby
Copied! ⭐️
class ArticlesController < ApplicationController
  def index
    debugger
    title = "Best Food"
    body = "Pizza, donuts, potatoes"
    @article = { title: title, body: body }
  end
end

The debugger statement will help us automatically open up articles_controller.rb. Then, make sure you're in the blog directory. Run the following command in your terminal:

text
Copied! ⭐️
rdbg -n --open=vscode -c -- bin/rails s

If successful, this should start rdbg, open a new VS Code instance automatically, attach VS code to the debugger, and start the Rails server.

Navigate to http://localhost:3000/articles/index, and you should automatically be taken to a breakpoint in articles_controller.rb from within the new VS Code instance.

Note that the above command may fail if you don't have the code command in your path. Visit VS Code's page on launching from the command line to get the code command added to your path. It's super simple. Open the Command Palette Command + Shift + P and type 'shell command' to find the Shell Command: Install 'code' command in PATH command.

Conclusion

In this tutorial, we have seen four ways we can debug a Ruby on Rails application using the debug gem and the rdgb debugger program. We can debug Rails application using either the command line or VS Code.

Pretty cool stuff, huh?! Will this be enough to switch Vim users over to VS Code? Probably not, but it sure is fun to debug Ruby and Rails application using VS Code with a shiny and customizable user interface! 😁

Resources