COO and technical lead, ME Tech · 2026
Upgrading a live school platform from Rails 4.2 to 7.1
- Ruby 2.5 / Rails 4.2 to Ruby 3.4 / Rails 7.1
- 8,000+ unit tests, from none
- Solo and part-time, about six months
The problem
ME Tech, a five-person software contracting company in Japan, looked after a school management platform used by two schools: about 800 students, plus teachers, owners and admins. It ran Ruby 2.5.8 and Rails 4.2, both long out of security support, stored photos with Paperclip, a deprecated gem, and had no automated tests, so nobody could change it with confidence.
A second codebase, the shared backend for two online reading-library and test sites, was on Rails 6.1 and needed the same work.
Constraints
- Solo and part-time. I did the upgrade alone, alongside my COO role.
- No tests. Nothing told me whether a change had broken something.
- Live schools. Staff and students used the platform throughout, and its behaviour couldn’t change without their agreement.
Approach
Tests before any version change
I started writing tests by hand, and it was too slow, so I persuaded the CEO to pay for Claude Code, and it wrote most of the tests. I wrote the factories (the code that builds test data) myself, because I didn’t like the generated ones, and gave the tests shared starting data so they all read the same way. I read every test before it went in.
Before changing any version, I raised line coverage to 85% (SimpleCov), starting with the code the upgrade would touch and the financial code.
Generated tests need rules. Mine were:
- Real records, not mocks. Tests build real records from factories. Mocks are only for things outside the models and controllers. Factories are slower, but they catch more.
- Every test checks a result. One generated test ran through the code without checking any output. It raised coverage and proved nothing. I added the missing branches and assertions.
- Tests describe what the code should do, not what it does today. One reporting test would have locked in an existing bug: as I recall, a student who had deferred a payment could be counted as paid.
One version at a time
- Ruby 2.5.8 and Rails 4.2 (the starting point)
- Ruby 2.7.8 and Rails 5.0
- Rails 5.1
- Rails 5.2: photos moved to Active Storage, released to production
- Rails 6.0: Paperclip removed
- Rails 6.1
- Ruby 3.4 and Rails 7.0
- Rails 7.1, released to production
One version at a time kept each step manageable: step up, fix what breaks, and replace code the new version deprecates. For each failing test I decided whether the test was weak or the code needed a real change, then fixed the test, the code or both without changing what the product did. Only the 5.2 and 7.1 steps went to production. I did the steps up to 7.0 by hand and used Claude Code for 7.1. Ruby 3.4 was already on the server for another project.
Hardest problems
Photos. Moving from Paperclip to Active Storage was the riskiest part: every page with a photo had to look the same afterwards. Dry runs with photos of different sizes checked that pages rendered and cropped them as before. A script moved the existing files, tried on staging first, and a second script checked every record against its file. It found a few Paperclip records pointing at files that didn’t exist. Paperclip stayed as a fallback, with a log message whenever Active Storage failed to serve a photo. No problems came up in production, and Paperclip was removed one step later.
Ruby 3.4. Jumping from Ruby 2.7 to 3.4 at Rails 7.0 broke BigDecimal handling, and I fixed it with a small patch. Some gems were no longer maintained, so I upgraded what I could and changed our code to work around the rest.
Bugs the tests turned up. Writing unit tests and checking flows end to end surfaced old bugs. I fixed the low-risk ones myself. Bigger ones went to the team and the staff who use the product, so we agreed on the fix before behaviour changed.
Results
- The platform moved from Ruby 2.5 and Rails 4.2 to Ruby 3.4 and Rails 7.1, with 8,000+ unit tests, in about six months part-time.
- The second codebase moved from Rails 6.1 to 7.1 with 500+ new tests in about a month.
- Between 30 and 50 bugs were fixed.
- Photos load about 40% faster and pages about 15% faster. These are rough averages from browser dev tools across several pages, before and after, not a formal benchmark.
- End-to-end checks ran on staging with test accounts: by hand first, then repeated with Claude driving the browser. There is no automated end-to-end suite.
What I’d do differently
- Start with Claude Code sooner. Writing the first tests by hand cost time. The review rules matter more than who types the tests.
- Agree the final version at the start. The business chose to stop at 7.1. I recommended going on to 8.0, because 7.1 no longer gets security fixes. Next time I’d put the final version, and the reasons for it, in the plan from the first day.
- Keep upgrade notes with the code. My detailed notes were company files and stayed behind when my role ended. Next time they’d be a runbook in the repository, which the client keeps.
If you have a Rails app stuck on an old version with few or no tests, I’d like to hear about it.