My Current Rails Setup (Ruby, Tools, Workflow)
After 12 years of Rails development, my setup has evolved from chaotic experimentation to a refined workflow that just works.
I've tried every shiny new tool, chased every productivity hack, and circled back to what actually makes me productive.
This isn't the only way to work with Rails. It's not even necessarily the best way. But it's what works for me after years of iteration, and maybe some of it will work for you too.
Ruby Version Management: rbenv
I use rbenv to manage Ruby versions. Not RVM, not asdf, not chruby. Just rbenv.
The reason is simple and boring. It works reliably and stays out of my way. I install it once, forget about it, and it just handles switching Ruby versions when I cd into different project directories.
I've used RVM in the past. It's powerful but heavy. It does too much. Gemsets, shell integration, all these features I don't need. Every time I opened a new terminal, there was this half-second pause while RVM did its thing. Small annoyance, but it added up.
rbenv is lightweight. It's just a shim in my PATH that intercepts Ruby commands and routes them to the right version. No shell magic. No startup delay. It reads the ruby-version file in my project, uses that Ruby, done.
Installation is straightforward on Mac with Homebrew. Just brew install rbenv ruby-build and add the init line to your shell config. On Linux, clone the repo and add it to PATH. Five minutes and you're done.
I keep a handful of Ruby versions installed. Whatever's current stable, usually one version back for legacy projects, and sometimes a preview release if I'm testing new features. Installing a new Ruby is just rbenv install three-three-six or whatever version.
Each project has a ruby-version file in the root specifying which Ruby to use. When I cd into that directory, rbenv automatically switches. No manual activation, no remembering to switch, just automatic.
The only gotcha I've encountered is remembering to rehash after installing gems with executables. When you install a gem that adds a command-line tool, you need to run rbenv rehash so rbenv sees it. But modern rbenv has a plugin that does this automatically, so even that's not an issue anymore.
Some people swear by asdf because it manages more than just Ruby. Node, Python, whatever. That's fine if you need it. I have Homebrew for everything else and prefer dedicated tools. rbenv does Ruby well. That's all I need.
Editor: RubyMine
I use RubyMine. Not VS Code, not Vim, not Emacs. A full IDE that costs money annually.
This is controversial. Developers have strong opinions about editors. Vim users think IDEs are bloated. VS Code users think paid tools are unnecessary. I've used both extensively and keep coming back to RubyMine.
The reason is autocomplete and intelligent refactoring that actually works. VS Code with the Solargraph extension gets you partway there. But RubyMine understands Ruby and Rails at a deeper level. It knows about ActiveRecord associations, Rails routes, view helpers, all of it.
When I type a model name and hit dot, RubyMine shows me all the methods including ones from included modules and ActiveRecord. Not just methods defined in that file, but everything available on that object. This works across the entire codebase. It follows associations, understands Rails magic, and rarely gets it wrong.
Refactoring is where RubyMine shines. Rename a method and it updates every reference across the entire project, including in views and tests. Extract a variable or method and it does it correctly, handling scope and parameters properly. Inline a variable and it works. These sound like basic features but most editors don't do them reliably for Ruby.
The debugger integration is excellent. Set breakpoints, step through code, inspect variables, all from the editor. No switching to a terminal, no binding.pry scattered through code. Just click in the gutter and debug.
The built-in terminal is good enough that I rarely leave the IDE. Database tools let me query and inspect the database without opening a separate client. Git integration is solid. I can do most of my work without switching applications.
The downsides are real. RubyMine is heavy. It uses a lot of RAM, maybe two to three gigabytes for a large project. It's Java-based so startup is slower than native editors. On my M1 Mac it's fine, but on older hardware it could be sluggish.
It also costs money. A hundred and ninety-nine dollars per year for the first year, less if you renew. For professional work where I'm billing clients or employed full-time, this is negligible. The productivity gain pays for itself in a day. For hobbyists or students, the cost might not be worth it.
I've tried to switch to VS Code multiple times. It's fast, it's free, everyone uses it. I'll use it for a week, miss intelligent refactoring, and come back to RubyMine. The features I use daily in RubyMine either don't exist or work poorly in VS Code.
That said, I keep VS Code installed for quick edits and non-Ruby work. Opening a single file, editing JSON or YAML, quick Git operations. VS Code launches instantly and is perfect for that. But for actual Rails development, RubyMine is my home.
Testing: RSpec
I use RSpec for testing. Not Minitest, not the new hotness like Playwright or whatever. Just RSpec, the same framework I've used for a decade.
Minitest is fine. It's built into Rails now, it's fast, it's simple. But RSpec's syntax feels more natural to me. The describe and it blocks read like documentation. The let syntax for test data is cleaner than setup methods. Matchers like expect-parenthesis-user-dot-name-close-parenthesis-dot-to-eq-parenthesis-Alice-close-parenthesis are more readable than assertions.
More importantly, the ecosystem is mature. FactoryBot for test data works seamlessly with RSpec. Database Cleaner integrates well. There are battle-tested patterns for request specs, model specs, system specs. I know how to structure tests because I've written thousands of them in RSpec.
System specs with Capybara let me test full user workflows. Click buttons, fill forms, assert content appears. These tests are slow but catch integration issues that unit tests miss. I write fewer of them, focusing on critical paths like authentication and checkout flows.
Request specs test API endpoints and controller behavior. Faster than system specs, still testing the full stack through the HTTP layer. I use these heavily for APIs and controller logic that doesn't need browser interaction.
Model specs test business logic in isolation. These are the fastest tests and the bulk of my test suite. Validations, associations, methods, scopes, all tested at the model layer.
I also use RSpec for testing background jobs, mailers, and service objects. The same framework across the entire application means I don't context-switch between testing styles.
My typical spec setup includes FactoryBot for fixtures, Faker for random data, and SimpleCov for coverage reports. I don't obsess over coverage percentage but I like seeing which code is exercised.
I run tests with spring for faster startup in development. The first run is slow while Rails loads, but subsequent runs are fast. I also use guard to automatically run tests when files change. Save a file, tests run, feedback loop is tight.
For CI, I use GitHub Actions running the full suite. Pull requests don't merge until tests pass. This catches issues before they reach main branch. Simple but effective.
The one thing I don't do is TDD religiously. I'll write tests first for complex logic where I need to think through edge cases. But for straightforward CRUD or UI changes, I'll write code first and tests after. Dogmatic TDD slows me down. Pragmatic testing keeps code reliable without ceremony.
Linting: RuboCop
I use RuboCop to enforce code style. Not manually, not through code review comments, but automatically with a linter that fails CI if code doesn't comply.
This removes all debate about style. No discussions about single quotes versus double quotes, whether to use parentheses in method calls, how many spaces to indent. RuboCop has opinions, we follow them, done.
I use the default cops with minimal customization. Maybe I'll disable a cop that's annoying for my specific use case, but mostly I accept RuboCop's defaults. Fighting the tool is counterproductive.
The configuration is minimal. Inherit from the default, set max line length to a hundred and twenty because eighty is too restrictive for modern screens, maybe exclude some files like schema.rb or generated code. That's it.
I run RuboCop with auto-correct enabled. Most violations get fixed automatically. Whitespace, indentation, quotes, all the mechanical stuff. The few that require manual fixes are usually worth addressing.
In my editor, RuboCop runs on save through a plugin. I see violations immediately and fix them before committing. This prevents the annoying cycle of pushing code, CI fails on RuboCop, fix locally, push again.
For legacy projects with thousands of violations, I use rubocop-todo. This generates a configuration file that disables all current violations. Then I gradually fix them over time. New code must comply, old code gets cleaned up incrementally.
Some developers hate linters. They see them as oppressive tools that enforce arbitrary rules. I see them as removing mental overhead. I don't think about code style. I write code, RuboCop formats it, I move on. One less thing to think about.
The alternative is inconsistent style across the codebase. One file uses single quotes, another uses double. One file has trailing whitespace, another doesn't. This inconsistency creates friction when reading code. Your brain has to parse different styles instead of focusing on logic.
With RuboCop, every file looks the same. You can jump between files written months apart by different developers and the style is consistent. This might seem superficial but it genuinely makes codebases easier to work with.
Database: PostgreSQL with Postico
I use PostgreSQL for every Rails project. Not MySQL, not SQLite in production. Just Postgres.
The reason is features and reliability. Postgres has everything I need. Full-text search, JSON columns, array columns, window functions, CTEs, all built-in. When I need advanced database features, they're there.
It's also rock-solid. I've never lost data in Postgres. I've never had corruption issues. It just works. The defaults are sane, performance is good out of the box, and scaling up is straightforward when needed.
For local development, I install Postgres with Homebrew on Mac or apt on Linux. Postgres.app is another good option on Mac if you prefer a GUI installer. Either way, it runs locally on standard port five-four-three-two.
For interacting with the database, I use Postico on Mac. It's a clean, native database client. I can browse tables, run queries, inspect schemas, all from a nice interface. It's not free, there's a paid version after trial, but it's worth it for daily use.
I also use psql occasionally for quick queries or when SSH'd into a server. But for regular database work, Postico is nicer.
One pattern I follow religiously is migrations that are safe in production. No dropping columns without a multi-step migration. No renaming columns that might break running code. Always additive changes first, then deprecate and remove in later releases.
I also ensure every migration is reversible. The down method should actually work. This makes it possible to roll back deployments if needed. Too many developers write irreversible migrations and then can't roll back when things go wrong.
Gems I Use on Every Project
Some gems show up in every Rails app I build.
Devise for authentication. I could write auth from scratch but why? Devise is battle-tested, secure, and handles edge cases I'd probably miss. Install it, configure it, move on.
Pundit for authorization. Simple policy-based authorization that's easy to understand and test. Each model gets a policy class defining who can do what. Clean and explicit.
Sidekiq for background jobs. Fast, reliable, and integrates perfectly with Rails. I use it for sending emails, processing uploads, calling external APIs, anything that shouldn't block a web request.
Pagy for pagination. Faster and simpler than Kaminari or will_paginate. Does one thing well with minimal overhead.
Administrate or ActiveAdmin for admin interfaces. Generating CRUD interfaces is tedious. These gems scaffold a basic admin in minutes. I can always customize later if needed.
Faker for test data. Generating realistic random data for tests and seeds. Names, emails, addresses, all the stuff you need for realistic fixtures.
Dotenv for environment variables in development. Keep secrets out of code and version control. Load them from a dotenv file locally. Deploy to production with real environment variables.
Version Control: Git with Conventions
I use Git for version control. Obviously. But how I use Git has evolved over the years.
I commit early and often. Small commits with focused changes. Each commit should do one thing and have a clear message explaining what and why.
Commit messages follow a convention. First line is a summary in present tense, under fifty characters. Then a blank line, then detailed explanation if needed. This makes git log readable and helps when searching history later.
I use feature branches for everything. Main branch is always deployable. New features get a branch, work happens there, pull request when ready, merge after review and tests pass.
Branch names are descriptive. Not just "fix-bug" but "fix-user-authentication-timeout-issue". This makes it easy to find branches later and understand what work was happening.
I rebase instead of merge when possible. Keeps history linear and cleaner. But I don't rewrite shared history. Once a branch is pushed and others might have based work on it, no more rebasing.
I use interactive rebase to clean up commits before merging. Squash small fixes, reorder commits logically, rewrite messages for clarity. The history on main should tell a clean story.
For collaboration, GitHub pull requests with required reviews. No direct pushes to main. Everything goes through a PR. This ensures code gets reviewed and tests run before merging.
Deployment: Dokku
I deploy small to medium projects on Dokku. Not Heroku, not Kubernetes, not fancy multi-region setups. Just a simple VPS running Dokku.
Dokku gives you Heroku-like workflow on your own server. Git push to deploy. Automatic SSL with Let's Encrypt. Environment variables. Database add-ons. All the Heroku conveniences at VPS prices.
A five-dollar-per-month DigitalOcean droplet runs several Dokku apps comfortably. The same apps on Heroku would cost fifty to a hundred dollars monthly minimum. The math is easy.
Setup is straightforward. Create a VPS, install Dokku, configure DNS, done. Then creating an app is just dokku apps colon create app-name. Push code, Dokku builds and deploys automatically.
For databases, Dokku has plugins. PostgreSQL, Redis, MySQL, whatever you need. Create a database, link it to an app, DATABASE_URL gets set automatically. Simple.
The tradeoff is you're responsible for the server. Updates, monitoring, backups. Heroku handles this for you. With Dokku, you handle it yourself or pay for managed services.
For small projects where I'm willing to handle ops, Dokku is perfect. For projects that need high availability or where I don't want to think about servers, I'll use a managed platform. But Dokku is my default for personal projects and small client work.
The Rest of the Toolchain
A few other tools round out the workflow.
Insomnia for API testing. Clean interface for building and testing HTTP requests. I used to use Postman but it got bloated. Insomnia is lighter and does what I need.
TablePlus as an alternative to Postico when I need to work with multiple database types. It supports Postgres, MySQL, Redis, all from one app. Not as polished as Postico for Postgres specifically but more versatile.
Alfred on Mac for quick launching and clipboard history. Spotlight on steroids. Launch apps, search files, run scripts, all from a keyboard shortcut.
iTerm2 for terminal. Better than default Terminal.app. Split panes, better search, profiles for different environments.
Homebrew for package management on Mac. Everything installed through brew is easy to update, easy to remove, version controlled in Brewfile.
1Password for secrets management. API keys, database passwords, all stored encrypted. The CLI integration lets me fetch secrets in scripts securely.
What I Don't Use
Some tools are popular but I don't use them.
Docker in development. I know it's standard at many companies. But for Rails, I find it adds complexity without much benefit locally. Rails runs fine on Mac or Linux directly. Save Docker for production deployments if needed.
Webpack or heavy JavaScript build processes. Rails with import maps handles JavaScript without build steps. I avoid the npm ecosystem when possible. It's complex and changes constantly.
GraphQL. REST APIs work fine for everything I build. GraphQL is powerful but adds significant complexity. Unless you're building something that needs its flexibility, stick with REST.
Microservices. Monoliths are fine. Most applications don't need to be split into services. Start with a monolith, extract services only when absolutely necessary.
The latest JavaScript framework. React, Vue, Svelte, whatever. Hotwire handles most interactive needs without the complexity. I reach for JavaScript frameworks only when Hotwire genuinely can't solve the problem.
The Philosophy
My setup isn't about using the newest tools or the most powerful tools. It's about using reliable tools that stay out of my way.
I want to think about the problem I'm solving, not fight my tools. RubyMine might use more RAM than VS Code, but it makes me more productive. RSpec might be heavier than Minitest, but I know it deeply. RuboCop might be opinionated, but it removes decisions.
The setup has evolved over years of trying different things and keeping what works. It'll probably evolve more. But right now, this is what lets me ship Rails applications efficiently without fighting my environment.
Your setup might look different. That's fine. Use what makes you productive. But if you're just starting with Rails or feeling friction in your current setup, maybe some of this will be useful.
The goal is to remove friction between your ideas and working software. Whatever setup does that for you is the right setup.