Legacy Rails App Health Check: A Senior Dev's Assessment Checklist

March 16, 2026 · 12 min read

You Already Know Something’s Wrong

You’re the senior dev on a Rails app that’s been in production for years. You feel the drag every day — slow CI runs, gems that haven’t been updated since the pandemic, a Rails version that’s two or three behind current. Deploys make you nervous. New hires take months to get productive. You know this needs attention, but “it feels bad” doesn’t get budget.

What you need is a number. Something concrete that turns a gut feeling into a business conversation. That’s what this checklist gives you.

Set aside an afternoon. Run through each section, score your app, and you’ll walk away with a clear picture of where you stand — and a document you can hand to your engineering manager or CTO that speaks in terms of risk, not feelings.

Section 1: Runtime and Framework Versions

This is the foundation. Everything else depends on whether your runtime and framework are still supported.

Ruby Version

Check your current Ruby version:

ruby -v

Now check it against the official Ruby maintenance page. Ruby versions get roughly three years of support: two years of normal maintenance, then one year of security-only fixes.

Status Score
Current or previous stable release (receiving normal maintenance) 0 — You’re fine
Security maintenance only 2 — Plan your upgrade
End of life 5 — This is urgent

If your Ruby is EOL, you are running on a runtime with known, unpatched vulnerabilities. Full stop. This isn’t theoretical risk — it’s the kind of thing that fails security audits and violates compliance requirements.

Rails Version

bin/rails version

Check against the Rails maintenance policy. Rails supports the current and previous major release with bug fixes. Only the current major release gets new features. Older versions get security patches only, if that.

Status Score
Current major release 0
Previous major release (bug fix support) 1
Two majors behind (security patches only) 3
Three or more majors behind / EOL 5

How Far Behind Are You?

Count the number of significant version boundaries between your current Rails version and the latest. Each boundary is its own upgrade project with its own set of breaking changes.

Distance Score
0–1 versions behind 0
2 versions behind 2
3 versions behind 4
4+ versions behind 5

Section 1 maximum: 15 points

Section 2: Dependency Health

Your Gemfile is a liability ledger. Every gem is code you didn’t write, maintained by someone you don’t control.

Known Vulnerabilities

gem install bundler-audit
bundle audit check --update

This checks your gems against the Ruby Advisory Database. Count the results.

Vulnerabilities Score
0 0
1–2 (low severity) 2
3+ or any high/critical severity 5

If bundle audit returns critical vulnerabilities, stop reading this checklist and fix them. Today.

Gem Currency

bundle outdated --only-explicit

Focus on your direct dependencies, not transitive ones. What you’re looking for:

  • Abandoned gems: No releases in 2+ years. Check the repo — is it archived? Are issues piling up with no responses? Each abandoned gem is a ticking time bomb that will block your next Rails upgrade.
  • Major version gaps: A gem at 2.x when 5.x is current means multiple breaking-change boundaries to cross.

Count your abandoned or heavily outdated direct dependencies:

Abandoned/outdated gems Score
0–2 0
3–5 2
6–10 4
10+ 5

JavaScript Dependencies

Don’t forget the other side of your dependency tree:

yarn audit 2>/dev/null || npm audit
JS vulnerabilities Score
0 or low only 0
Moderate 2
High or critical 4

Section 2 maximum: 14 points

Section 3: Test Coverage and CI Health

Tests are your safety net. Without them, every change is a gamble and every upgrade is a leap of faith.

Coverage

If you have SimpleCov or a similar tool configured, check your current coverage percentage. If you don’t have coverage tracking at all, that’s its own answer.

COVERAGE=true bin/rails test
# Check coverage/index.html or your CI dashboard
Coverage Score
80%+ meaningful coverage 0
60–79% 2
40–59% 4
Below 40% or no coverage tracking 5

“Meaningful” matters here. 90% coverage that’s all assert_response :success with no validation of behavior is worse than 60% that actually tests business logic. Use your judgment.

Test Suite Health

Run your full suite right now:

bin/rails test
Result Score
All green, under 10 minutes 0
All green, over 10 minutes 1
Fewer than 5 failures 3
Significant failures or suite won’t run 5

A test suite that doesn’t pass is a test suite that doesn’t get run. And a suite that takes 30 minutes means developers skip it locally and wait for CI — which means broken code gets pushed regularly.

CI Pipeline

Status Score
CI runs on every PR, enforced, green 0
CI exists but isn’t enforced or is frequently red 3
No CI 5

Section 3 maximum: 15 points

Section 4: Security Posture

Beyond gem vulnerabilities, there are application-level security concerns that accumulate in aging codebases.

Authentication and Authorization

Is your authentication solution current and maintained? Apps on older stacks often rely on Devise versions that haven’t been updated, custom authentication written years ago, or — worst case — hand-rolled session management with known weaknesses.

Status Score
Current, maintained auth solution with MFA support 0
Maintained but outdated auth (no MFA, old patterns) 2
Unmaintained or hand-rolled auth 5

Security Headers and Configuration

# Check your security headers — visit your app and inspect response headers
curl -I https://your-app.com 2>/dev/null | grep -iE "strict-transport|content-security|x-frame|x-content-type"
Status Score
HSTS, CSP, X-Frame-Options, X-Content-Type-Options all present 0
Some headers present 2
No security headers 4

Secrets Management

How does your app handle secrets? Check for these red flags:

  • Secrets committed to version control (check git history, not just current state)
  • Hardcoded API keys in initializers
  • Shared credentials across environments
  • No rotation policy
Status Score
Encrypted credentials or proper secrets manager, no history of committed secrets 0
Environment variables with reasonable practices 1
Secrets in config files, committed to repo, or shared across environments 5

Section 4 maximum: 14 points

Section 5: Performance and Operational Health

An app can be on a current stack and still be operationally unhealthy. These indicators tell you whether the app is sustainable to run.

Database

-- Check for missing indexes on foreign keys (run in your DB console)
SELECT c.conrelid::regclass AS table_name,
       a.attname AS column_name
FROM pg_constraint c
JOIN pg_attribute a ON a.attnum = ANY(c.conkey) AND a.attrelid = c.conrelid
WHERE c.contype = 'f'
AND NOT EXISTS (
    SELECT 1 FROM pg_index i
    WHERE i.indrelid = c.conrelid
    AND a.attnum = ANY(i.indkey)
);

Also check for:

  • Tables with millions of rows and no archiving strategy
  • Migrations that haven’t been squashed (hundreds of migration files)
  • Missing database-level constraints that only exist in Rails validations
Status Score
Indexes optimized, constraints in place, migrations manageable 0
Some missing indexes or constraints, but no critical issues 2
Significant missing indexes, no DB constraints, unbounded table growth 5

Error Tracking and Monitoring

Status Score
Error tracker (Sentry, Honeybadger, etc.) with alerts, monitored regularly 0
Error tracker exists but unmonitored or noisy 2
No error tracking 5

Deployment

Status Score
Automated deploys, rollback capability, under 10 minutes 0
Semi-automated or slow but functional 2
Manual deploy process or “only Dave knows how to deploy” 5

Section 5 maximum: 15 points

Section 6: Code Quality

This is the most subjective section, but there are concrete signals.

Rails Best Practices

# Install and run the static analyzer
gem install rails_best_practices
rails_best_practices .

Don’t treat every warning as gospel, but the count gives you a rough sense of how far the codebase has drifted from conventions.

Warnings Score
Under 20 0
20–50 1
50–100 3
100+ 5

Complexity and Size

bin/rails stats

Look at your controller and model sizes. Fat controllers and god models are the classic Rails code smell.

Status Score
No file over 300 lines, clear separation of concerns 0
A few large files but mostly manageable 2
Multiple 500+ line models/controllers, business logic in callbacks 4
God objects, 1000+ line files, no discernible architecture 5

Deprecation Warnings

bin/rails test 2>&1 | grep -c "DEPRECATION"
Warnings Score
0 0
1–20 1
21–50 3
50+ 5

Section 6 maximum: 15 points

Scoring Your Results

Add up your scores across all six sections. Maximum possible: 88 points.

Total Score Health Rating What It Means
0–15 Healthy Normal maintenance. Keep doing what you’re doing.
16–30 Showing Age Targeted attention needed. Specific areas are falling behind but the app is fundamentally sound. Plan improvements over the next quarter.
31–50 At Risk Multiple compounding problems. The longer you wait, the more expensive every fix becomes. Dedicate resources this quarter.
51–70 Critical The app is a liability. Security risk, developer productivity is suffering, and upgrades will be expensive. This needs executive attention now.
71–88 Life Support Comprehensive intervention required. You’re likely past the point where incremental fixes make sense. See the modernize vs. rewrite section below.

Making the Case

You’ve got a number. Now you need to translate it into a conversation your engineering manager or CTO will engage with.

Don’t lead with the score. Lead with the business risk. Here’s a template:

“Our Rails app scores X out of 88 on a standardized health assessment. The three biggest risk areas are [specific findings]. Here’s what that means in practical terms: [concrete consequences — security exposure, developer productivity loss, upgrade costs increasing over time]. Here’s what I recommend we do about it, and what it’ll cost to wait.”

Attach the filled-out checklist as an appendix. The specificity is what makes this persuasive — you’re not saying “the app feels old,” you’re saying “we have 4 gems with known critical vulnerabilities, our Ruby version has been EOL for 14 months, and our test coverage is 38%.”

Tip: Frame the cost of inaction, not just the cost of action. “Upgrading Rails will take X weeks” is easy to defer. “Every month we delay, we accumulate Y in additional upgrade cost and remain exposed to Z known vulnerabilities” is harder to ignore.

Modernize vs. Rewrite: The Decision Framework

You’ve scored your app and you know it needs work. The question is: do you fix what you have, or start over?

This is one of the highest-stakes decisions in software, and it gets made badly more often than any other. The instinct to rewrite is strong — a clean slate sounds so much better than untangling years of accumulated decisions. But rewrites fail far more often than modernizations, and they fail in a specific way: they take twice as long as estimated, reproduce half the original bugs, and introduce a crop of new ones.

When Modernization Is the Right Call

Modernization means incrementally upgrading, refactoring, and improving the existing codebase. Choose this path when:

  • The core architecture is sound. The app follows recognizable Rails conventions, even if it’s behind on versions. Models, controllers, and views are where you’d expect them. The request/response flow makes sense. You can trace a feature through the code without wanting to quit.
  • The score is under 50. You have specific, identifiable problems — not systemic rot. You can attack the highest-scoring sections individually.
  • Business logic is embedded in the code. Years of edge cases, special handling, and domain rules live in those models and service objects. A rewrite means re-discovering and re-implementing all of it. Some of it isn’t documented anywhere except the code itself.
  • The app is in active use. Users depend on it daily. A rewrite means maintaining two systems in parallel, or a risky cutover. Modernization lets you improve continuously without a flag day.
  • Your team knows the codebase. Institutional knowledge has enormous value. A team that understands the current system’s quirks will modernize it faster than they’ll rewrite it.

The modernization path looks like: fix critical security issues first, then upgrade Ruby and Rails version by version, replace abandoned gems, improve test coverage in the areas you’re changing, and refactor incrementally as you go. Each step makes the next one easier. You ship improvements continuously rather than going dark for months.

When a Rewrite Might Actually Be Justified

Rewrites are the right call in genuinely rare circumstances:

  • The score is 70+, and the architecture is the problem. Not just outdated dependencies — the fundamental structure is wrong. No separation of concerns. Business logic spread across views, controllers, callbacks, and raw SQL. No amount of incremental refactoring will fix a broken foundation.
  • The technology is a dead end. The framework or language has been abandoned by its community. You can’t hire for it. The ecosystem has moved on. (Note: Rails is emphatically not in this category.)
  • Requirements have fundamentally changed. The app was built for a different business model. It’s a monolith that needs to be a set of services, or vice versa. The data model is wrong at the entity level, not just the schema level.
  • You can afford it. A rewrite means months of development with no new features for the existing product. Maintaining two systems until cutover. The risk of the new system not reaching feature parity. If your business can’t absorb that, modernize instead.

Even when a rewrite is justified, consider a strangler fig approach: build new components alongside the old system, route traffic incrementally, and retire the old code piece by piece. It’s less dramatic than a flag-day rewrite and far less risky.

The Trap to Avoid

The most common mistake is rewriting because the codebase is unpleasant, not because it’s unsound. Ugly code that works, has tests, and follows a recognizable structure is a better starting point than a blank editor. Every working system encodes decisions that took time and pain to learn. A rewrite discards all of them.

If your score is in the 30–50 range and you’re tempted to rewrite, resist. That range is solidly in modernization territory. The app has real problems, but they’re tractable. A focused quarter of upgrade and refactoring work will transform it.

What Comes Next

If you’ve scored your app and it’s in the Healthy or Showing Age range: bookmark this checklist and re-run it quarterly. Maintenance is cheaper than remediation.

If you’re in the At Risk range: pick the two highest-scoring sections and build a concrete plan to address them this quarter. The Rails Upgrade Survival Guide covers the version upgrade path in detail.

If you’re in the Critical or Life Support range: you need experienced help, and you need it soon. The compound interest on technical debt is real — every month you wait makes the eventual fix more expensive.

If your scores feel disconnected from how your team is actually experiencing the codebase — the numbers say one thing but the frustration says another — Signs Your Rails App Has Outgrown Your Team looks at the organizational signals that often precede the technical ones.

Note: Scored your app and not sure what to do with the results? Let’s talk through it — I’ve assessed dozens of legacy Rails apps and I can help you prioritize what to fix first.

Need help with this?

No commitment — let's start with a conversation.

Let's Talk