Ruby on Rails Modernization: Five Upgrade Steps Without a Rewrite
A US healthcare platform depended on a legacy Rails app running Ruby 2.6 and Rails 5.1. Teamvoy moved it through five Rails upgrade steps, expanded the automated regression safety net, and kept normal product work moving.

| Project detail | Value |
|---|---|
| Client | A US healthcare platform |
| Industry | Healthcare technology |
| Starting point | Ruby 2.6, Rails 5.1 |
| Current project milestone | Ruby 3.3.12, Rails 7.1.6 |
| Main implementation | Approximately five weeks |
| Production rollout | Completed after staging validation |
| Status | Modernization ongoing; further Rails upgrades required |
| Primary service | Ruby on Rails Development Services |

— Five Rails upgrade steps and approximately five weeks of main implementation moved the application off its original Ruby 2.6 and Rails 5.1 baseline.
01. Summary
Ruby on Rails modernization moved a US healthcare platform’s application from Ruby 2.6 and Rails 5.1 to Ruby 3.3.12 and Rails 7.1.6 without a full rewrite
Teamvoy expanded the regression suite first, audited the dependency graph, upgraded one compatibility layer at a time, and validated major changes through CI and staging before production rollout. The visible result was five Rails upgrade steps in approximately five weeks of main implementation. The less visible result was the safety net: automated spec files increased from 199 to approximately 590, concentrated around billing, insurance, authentication, controllers, libraries, and infrastructure. That figure measures spec files, not a threefold increase in test coverage percentage. The team also made a deliberate scope decision. jQuery, React 16, Turbolinks, Webpacker 4, and MobX stayed in place because replacing them would not have solved the immediate Ruby and Rails support problem. The project reduced backend and runtime risk while leaving a separate frontend modernization backlog.
02. Problem
The legacy Rails app had become a product-delivery constraint, not just an untidy codebase
The platform uses the application for client and therapist workflows, scheduling, billing, feedback, and other operations involving sensitive healthcare and payment-related information. Ruby 2.6 and Rails 5.1 were outside their normal support windows, while outdated gems and framework integrations made future changes harder to sequence.
- Billing work still had to ship. The client could not freeze normal product development while a replacement system was built.
- Years of business rules had to survive. Billing, insurance, authentication, permissions, care workflows, and external integrations already encoded production behavior that a rewrite would have to rediscover and revalidate.
- The existing test suite did not protect every high-cost path. A framework regression could be difficult to distinguish from old application behavior without more characterization and regression coverage.
A full rewrite would have created two active systems, a longer validation period, and a larger regression surface. Teamvoy chose an incremental Rails upgrade instead: protect critical behavior, audit compatibility, make one controlled change, validate it, and continue.
03. Solution
The modernization separated test expansion, dependency work, Ruby changes, Rails version changes, and framework defaults
This made failures easier to attribute and kept each release small enough to review, validate, and roll back through an ordinary code revert and redeploy.
Regression Tests Before the First Ruby on Rails Upgrade
The first phase focused on coverage rather than version numbers. Teamvoy restored and expanded RSpec protection around P0 billing, insurance, and authentication flows, enabled SimpleCov, and created dedicated Billing & Insurance coverage groups. Test-writing ran in two streams: business-critical billing and insurance behavior, and controllers, libraries, and infrastructure. The number of automated spec files moved from 199 to approximately 590. That did not prove that every path was covered. It gave the team a much stronger signal when a gem, runtime, autoloader, configuration default, or framework behavior changed. Official Rails guidance recommends having good test coverage before upgrading and moving through Rails versions incrementally. The project applied the same principle to production risk: change fewer variables at once, then use CI and staging to identify what actually broke.
A Readiness Audit Set the Incremental Rails Upgrade Order
Before changing Rails, Teamvoy reviewed the Gemfile and dependency graph, Rails configuration, PostgreSQL integration, background jobs, frontend stack, CI, deployment process, and external integrations. The audit produced an ordered plan and exposed blockers such as inconsistent dependency requirements and obsolete Sidekiq integration patterns. Dependency sequencing turned out to be harder than changing Rails itself. Some gems had to be upgraded or removed. Others needed temporary pins while the application still ran on Ruby 2.6. The ffi dependency, for example, required compatibility handling before the runtime could move. This order prevented the framework upgrade from becoming a single branch containing every compatibility fix. Each blocker could be tested and reviewed against the version that introduced it.
Ruby, Rails, and Framework Defaults Moved Separately
Rails progressed through five public-facing steps: 5.1 to 5.2, 5.2 to 6.0, 6.0 to 6.1, 6.1 to 7.0, and 7.0 to 7.1. Ruby moved from 2.6 to 2.7.8 before reaching 3.3.12. The intermediate Ruby release reduced the number of simultaneous compatibility changes, especially around Ruby 3 keyword arguments and older gem assumptions. Framework defaults were treated as separate migrations. The application first ran on each newer Rails version while preserving compatible behavior. New defaults were then enabled in small groups. Rails 7 defaults, for example, were switched on in three controlled batches before the temporary defaults initializer was removed. The same approach applied to autoloading and assets. Migrating from the classic autoloader to Zeitwerk exposed class names and file paths that older Rails versions had tolerated. Uglifier was replaced with Terser, Active Storage configuration was added, and filter, PDF-layout, partial-lookup, redirect-protection, and cache-format behavior was corrected as distinct compatibility work.

– Ruby, Rails, dependencies, autoloading, and defaults moved as separate compatibility layers instead of one combined release.
The Frontend Stayed Outside the Modernization Boundary
Teamvoy deliberately retained jQuery, React 16, Turbolinks, Webpacker 4, MobX, and the broader frontend architecture. These technologies were also old, but replacing them was not required to remove the immediate Ruby and Rails baseline risk. That choice reduced scope and made regressions easier to trace. It also preserved technical debt. The project did not present a mixed-generation stack as the final state; it treated it as a controlled trade-off that kept product work moving while the runtime and framework changed underneath it.

– The modernization boundary included Ruby, Rails, critical dependencies, CI, and regression protection while the working frontend remained outside the immediate scope
Technology Used in the Modernized Application
| Layer | Current project milestone | Why it mattered |
|---|---|---|
| Runtime and framework | Ruby 3.3.12, Rails 7.1.6 | Replaced the original Ruby 2.6 / Rails 5.1 baseline and enabled further upgrades |
| Data and application runtime | PostgreSQL, pg 1.5, Puma | Preserved the existing application and data model rather than creating a replacement platform |
| Background processing | Sidekiq 6.5, Redis | Required compatibility updates across the framework/runtime sequence |
| Authentication and domain libraries | Devise, Pundit, PaperTrail, Paranoia, CarrierWave | Carried business and audit behavior that had to survive the upgrade |
| Testing | RSpec, FactoryBot, Capybara, WebMock/VCR, SimpleCov | Provided regression evidence before and after each compatibility step |
| CI and rollout | GitHub Actions, Docker, staging validation | Kept changes independently testable and deployable |
| Frontend retained | jQuery, Turbolinks, Webpacker 4, React 16, MobX | Stayed out of scope to limit regression risk and avoid combining two modernization programs |
The Main Upgrade Took About Five Weeks. The Program Continues.
The safety-net phase ran first, followed by approximately five weeks of main Ruby and Rails implementation. Major steps then moved through staging regression testing before production rollout. Normal product development could continue because compatibility fixes, framework hops, Ruby changes, and default batches remained independently deployable.

– The delivery path moved from regression protection to five upgrade steps, staging validation, and production rollout while further Rails modernization remained on the roadmap.
Rails 7.1.6 was a project milestone, not the end of the lifecycle. Official Rails support for the 7.1 series has ended. The supplied project plan identified Rails 7.2 as the next intermediate compatibility step, but that series is also outside the current maintenance window as of October 13, 2026. The roadmap must continue beyond it to a release covered by the official Rails maintenance policy. Ruby 3.3 remains in security maintenance according to the official Ruby branches schedule. The case therefore describes completed upgrade work without claiming the overall modernization program is finished.
04. Results
The project replaced an unsupported starting baseline with a substantially newer Ruby and Rails foundation while the product remained active
Its measurable outputs are version progression, delivery sequence, and the expanded regression suite – not unsupported claims about response time, infrastructure cost, release frequency, or feature velocity.
| Metric | Before | Project milestone | Status |
|---|---|---|---|
| Ruby runtime | Ruby 2.6 | Ruby 3.3.12 | Verified in supplied brief |
| Rails framework | Rails 5.1 | Rails 7.1.6 through five upgrade steps | Verified in supplied brief |
| Automated spec files | 199 | Approximately 590 | Verified count; not coverage percentage |
| Main implementation | No approved baseline | Approximately five weeks | Verified supplied window |
| Product delivery | Rewrite or freeze avoided | Normal product work could continue | Verified |
| Remaining modernization | Unsupported starting baseline | Further Rails upgrades still required | Ongoing |
The strongest outcome was a repeatable process. The team can continue from a larger regression safety net, a newer runtime, updated framework-critical dependencies, Zeitwerk-compatible code, and an upgrade sequence already tested in CI, staging, and production.
Four Modernization Facts
- Five Rails transitions: 5.1 → 5.2 → 6.0 → 6.1 → 7.0 → 7.1.
- Two controlled Ruby stages: 2.6 → 2.7.8 → 3.3.12.
- Approximately 590 automated spec files: up from 199, without presenting the change as a coverage percentage.
- A deliberately limited scope: the frontend rewrite stayed outside the modernization workstream.
05. Conclusion
This approach worked because Teamvoy treated modernization as risk management rather than a dependency update
Tests protected the workflows with the highest business cost. Dependencies moved before the framework when necessary. Ruby, Rails, and Rails defaults changed separately. Unrelated frontend work stayed outside the boundary. The result was not “everything modern.” It was a production application moved several framework generations forward without a rewrite, with clearer regression evidence and a repeatable path for the remaining upgrades.
Talk to Teamvoy About a Legacy Rails App
If your Rails application is blocked by unsupported versions, abandoned dependencies, or a test suite that cannot distinguish a regression from old behavior, Teamvoy can map the upgrade sequence before the first framework change.