Debugging Ruby APIs using Visual Studio Code

**Note: if using Ruby 2.5.0, please use version '0.2.2.beta14' for the debase gem. **

Recently I have been looking at using Visual Studio Code as my IDE of choice for Ruby Development.

In the past I have always used RubyMine, but have felt that it is time to look at what else is available for Ruby development.

As someone who mainly writes Restful APIs in Ruby using gems such as Grape or Sinatra, and who rarely writes Rails Applications, I have in the past found it more difficult to debug my API using other IDEs.

In RubyMine this is a fairly simplistic process, simply:

  • Write some code
  • Create a config.ru file
  • Generate a new run configuration for "Rack" which points at the config.ru file
  • Click "debug"

With Visual Studio Code, its not an IDE whose primary Language is Ruby, therefore there are some processes that need to be put into place to make it as simple to debug Rack applications as RubyMine..

The Setup

The first thing was to configure VSC for Ruby, for this, I use the extension found here. To install:

  1. Press F1
  2. Then search for ext install and select "Extensions: Install Extensions"
  3. Search for "Ruby" and install (you might need to restart the IDE)

Once this has been installed, it is time to start to think about debug configurations.

When selecting the debug icon on VSC, if there has been no configurations before, next to the play button at the top, there will be option to create a new configuration. This will usually prompt what type of language support is required (select Ruby).

This will generate a default template for launch configurations (the file is placed under .vscode/launch.json).

A quick look at this file, and it shows that there are some default run configurations:

The default include options to run a Rails server, or Rspec or even just a single file. But we want to debug our API which is using Grape on Rack..

To make this work we need to add a new configuration to this file:

{
  "name": "Run API",
  "type": "Ruby",
  "request": "launch",
  "cwd": "${workspaceRoot}",
  "useBundler": true,
  "pathToBundler": "${env:HOME}/.rvm/gems/${env:RUBY_VERSION}/wrappers/bundle",
  "showDebuggerOutput": true,
  "pathToRDebugIDE": "${workspaceRoot}/bin/rdebug-ide",
  "program": "${workspaceRoot}/bin/rackup",
  "args": [
    "-p", "9292",
    "-E", "development"
  ]
}

Note The above snippet would work only when using RVM for your ruby version manager, if using RBENV, then pathToBundler would need to change to reflect this: ${env:HOME}/.rbenv/shims/bundle has been working for me.

The above will start up our API and it will be available for debugging. But how does it work?

Firstly there are a couple of key points:

  • "useBundler" this is making sure that it will run under bundler, for example bundle exec rackup
  • "pathToBundler" This is making sure that it is using the correct bundler. A couple of notes regarding this line:
    • the first part will eval to ~/
    • {env:RUBY_VERSION} will eval to the current version of ruby that RVM is configured to be "current" ( you can see this list running rvm list
    • Using these ENV variables, allows this configuration to be shared amongst multiple projects and colleagues.
    • If using RBENV see the above note
  • "pathToRDebugIDE" this is an additional Gem that is required to be installed in the gem file for this to work. It is the same gem that RubyMine uses to debug the application. The location (/bin/) is where bundler will install the binstubs (more on this in a moment)
  • the "-E" argument for development is not strictly required as it is the default, I just like to be explicit.

So that is great, we have a configuration setup to run our API in debug mode. However this will not work right off the bat. It requires a couple of additions.

  • When installing the gems, it is required that the binstubs are installed also. This is done by passing the argument: bundle install --binstubs --path vendor/bundle
  • A couple of additional Gems are required, I would recommend adding this to your development group, so that they dont get added to production:
group :development do
    gem 'ruby-debug-ide', '~> 0.6.0'
    gem 'debase', '~> 0.2.1'
end

Gotchas

One of the things that I would usually do when developing APIs is configure everything via the environment this is something that is explained in the 12 factor app.

However doing this with the runner configuration, would be a pain in the backside to edit each time and for each project. Therefore for development I make use of the dotenv gem which allows me to have a .env file at the root of my project and then load in when the API starts (provided it is in development mode).

if ENV['RACK_ENV'] == 'development'
  require 'dotenv'
  Dotenv.load
end

(This is one of the reasons I am explicit about what env Rackup is running in!)

I always add the dotenv gem to the development group in my Gemfile as well.

git

I would always look at adding the following to my .gitignore file to keep them out of source control:

  • bin/**/*
  • vendor/**/*

Note Originally I had thought that it might be better not to commit the .vscode file, however after some thought process, if multiple developers are going to work on the application it might be simpler to commit so that they dont have to go through the process of setting up as we have had to here..

And if using donenv:

  • .env

Scripting it up!

So this is useful, I am now able to debug my Rack API. But it is a PITA to do this for each project, making sure that gems are installed the correct way, and to copy over the config.

Therefore I decide to write a helper BASH script to setup my Ruby VSC projects.

The script has two arguments:

  1. The directory where the project lives
  2. The Ruby Manager (rvm or rbenv)

The script

The script above will do the following:

  1. CD in to the directory that was passed with the -d flag
  2. Run bundle install --quiet --binstubs --path vendor/bundle
  3. Create a .vscode directory if it doesn't exist
  4. Create a launch.json file in the .vscode directory with the following preconfigured debug configurations:
    • A single file
    • Rackup API
    • Rspec (all specs)
    • Rspec (current open spec file)

Usage

Feel free to use the above script, and please feedback if it has been helpful or if there are any suggestions on how to improve.

Debugging Ruby APIs using Visual Studio Code
Share this