Editor Setup for Rails — The Extensions and Keybindings Worth Keeping
There’s a genre of blog post that lists forty editor extensions. You install all forty, your editor takes twelve seconds to start, three of them fight over formatting, and you uninstall the lot in a month. A Rails setup needs about six things, and most of the value comes from one of them — the language server — plus a handful of keybindings that remove the friction between “I want to see that file” and seeing it.
Get the Language Server Right First
Everything else is decoration. Ruby LSP (maintained by Shopify) is the current default and it does the work three separate extensions used to do.
Example:
# Gemfile
group :development do
gem "ruby-lsp", require: false
end
What you get: go-to-definition, find-references, inline diagnostics, rename symbol, document symbols, and semantic highlighting that knows the difference between a local variable and a method call.
The Rails add-on matters
group :development do
gem "ruby-lsp", require: false
gem "ruby-lsp-rails", require: false
end
This is what turns a Ruby language server into a Rails one. It resolves belongs_to :account to the Account model, jumps from a controller action to its view, and knows about your database schema — hovering a model shows its columns and types, which removes most of your trips to schema.rb.
Configuration
// .vscode/settings.json — commit this
{
"[ruby]": {
"editor.defaultFormatter": "Shopify.ruby-lsp",
"editor.formatOnSave": true,
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.semanticHighlighting.enabled": true
},
"rubyLsp.formatter": "rubocop",
"rubyLsp.enabledFeatures": {
"diagnostics": true,
"formatting": true,
"codeActions": true,
"inlayHint": true
},
"files.associations": {
"*.html.erb": "erb",
"Gemfile": "ruby",
"*.jbuilder": "ruby"
},
"files.exclude": {
"**/tmp": true,
"**/log": true,
"**/node_modules": true,
"**/.git": true
}
}
Committing .vscode/settings.json is worth doing. It’s not imposing an editor on anyone — people who don’t use VS Code ignore the file — but it means everyone who does gets the same formatter and tab width, which eliminates a whole category of noisy diffs.
files.exclude on tmp and log matters more than it looks: without it, your fuzzy file finder is full of cached ERB and your search results include every line of development.log.
The Extensions That Earn Their Place
Six, for Rails work:
- Ruby LSP (Shopify) — covered above, does the heavy lifting
- Ruby LSP Rails — the Rails-aware half
- ERB Formatter/Beautify or erb-lint — ERB indentation is genuinely painful without it
- Endwise — auto-inserts
endwhen you typedef,do,if. Small, constant payoff - GitLens — inline blame on the current line, which answers “why is this here” without leaving the file
- Rails Test Runner (or the built-in task runner) — run the spec under the cursor with one keystroke
That’s it. Notable omissions and why:
- Separate RuboCop extension — Ruby LSP runs it. Two things running RuboCop will fight.
- Solargraph — Ruby LSP supersedes it. Running both is a common cause of duplicated completions and slow saves.
- Snippet packs — LSP completion is better and doesn’t go stale.
- Bracket colourizers — built into the editor now.
Keybindings Over Extensions
The highest-leverage change isn’t an extension, it’s binding the four things you do constantly.
Example:
// keybindings.json
[
{
"key": "cmd+shift+t",
"command": "workbench.action.tasks.runTask",
"args": "rspec: current file"
},
{
"key": "cmd+shift+l",
"command": "workbench.action.tasks.runTask",
"args": "rspec: current line"
},
{
"key": "cmd+shift+r",
"command": "workbench.action.tasks.runTask",
"args": "rspec: last failed"
},
{
"key": "ctrl+cmd+left",
"command": "workbench.action.navigateBack"
}
]
Example:
// .vscode/tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "rspec: current file",
"type": "shell",
"command": "bundle exec rspec ${relativeFile}",
"presentation": { "panel": "dedicated", "clear": true },
"problemMatcher": []
},
{
"label": "rspec: current line",
"type": "shell",
"command": "bundle exec rspec ${relativeFile}:${lineNumber}",
"presentation": { "panel": "dedicated", "clear": true },
"problemMatcher": []
},
{
"label": "rspec: last failed",
"type": "shell",
"command": "bundle exec rspec --only-failures",
"presentation": { "panel": "dedicated", "clear": true },
"problemMatcher": []
}
]
}
“Run the spec at my cursor” is the single keystroke that most changes how you work, because it collapses the write-test / switch-to-terminal / retype-path loop into one key. --only-failures requires config.example_status_persistence_file_path set in spec_helper.rb, which is worth doing anyway.
Navigation bindings
navigateBack (Ctrl+Cmd+← on macOS, Alt+← elsewhere) is the one people don’t bind and should. Jump to a definition, read it, jump back. Without it, following a call chain three deep means manually finding your way home.
Neovim, Briefly
The same language server, different host.
Example:
-- lazy.nvim
{
"neovim/nvim-lspconfig",
config = function()
require("lspconfig").ruby_lsp.setup({
init_options = {
formatter = "rubocop",
enabledFeatures = { "diagnostics", "formatting", "codeActions" },
},
})
end,
}
Add vim-rails for :Emodel, :Eview, :A (alternate between a file and its spec), and gf on a partial name. Those navigation commands predate LSP and are still better than anything an LSP provides, because they understand Rails conventions rather than symbols.
:A — jump between app/models/order.rb and spec/models/order_spec.rb — is worth the plugin on its own.
Pro-Tip: Add a
.vscode/settings.jsonwithfiles.watcherExcludefortmp/**,log/**, andnode_modules/**, not justfiles.exclude. They’re different settings and people set the wrong one.files.excludehides paths from the sidebar and search;files.watcherExcludestops the editor from putting a filesystem watcher on them. A Rails app in development writes totmp/cacheandlog/development.logconstantly, and an editor watching those directories burns CPU permanently, triggers spurious reload events, and on macOS will eventually exhaust the file-descriptor limit and start silently failing to notice real file changes. The symptom is “my editor stopped picking up changes and a restart fixes it” — that’s the watcher limit, and this one setting fixes it for good.
Keep the Configuration in the Repo
Two files, both worth committing:
.vscode/settings.json— formatter, tab width, exclusions. Prevents formatting-war diffs..vscode/tasks.json— the spec-running tasks above. New team members get them for free.
Also worth adding, and often forgotten:
// .vscode/extensions.json
{
"recommendations": [
"Shopify.ruby-lsp",
"eamodio.gitlens",
"aliariff.vscode-erb-beautify"
]
}
VS Code prompts new contributors to install these when they open the repo. It’s the closest thing to a documented editor setup that people actually follow.
What not to commit: keybindings (personal), colour themes (personal), and anything referencing an absolute path from your machine.
Conclusion
Ruby LSP plus ruby-lsp-rails covers definitions, references, diagnostics, formatting, and schema-aware model hovering — get that working properly and you can skip most of the extension lists. Add ERB formatting, endwise, and GitLens, and stop there; overlapping extensions cause more problems than they solve. Bind “run the spec at my cursor” and “navigate back” — those two keystrokes change your working loop more than any plugin. Commit settings.json, tasks.json, and extensions.json so the team shares a baseline, exclude tmp and log from both search and the file watcher, and keep your personal keybindings out of the repo.
FAQs
Q1: Ruby LSP or Solargraph?
Ruby LSP. It’s actively developed, faster on large codebases, and has the Rails add-on. Running both simultaneously causes duplicate completions and conflicting formatting.
Q2: Why is go-to-definition slow on my project?
Ruby LSP indexes on first run, which takes a while on a large app. If it stays slow, check that tmp and node_modules are excluded — indexing generated files is a common cause.
Q3: Should I use RuboCop or Standard for formatting?
Whatever your project already uses — set rubyLsp.formatter to match. The important thing is that everyone’s editor produces the same output, not which tool does it.
Q4: How do I get ERB files formatting correctly?
Ruby LSP doesn’t format ERB. Use erb_lint with an editor extension, or aliariff.vscode-erb-beautify. Configure it in .erb-lint.yml so the CLI and the editor agree.
Q5: Is a JetBrains IDE worth paying for on Rails work?
RubyMine’s refactoring and Rails-aware navigation are genuinely ahead, especially on large legacy codebases. On a well-organised modern app, VS Code with Ruby LSP closes most of the gap for free.
Check viewARU - Brand Newsletter!
Newsletter to DEVs by DEVs - boost your Personal Brand & career! 🚀