I still maintain quake.org.pl - a site established around 1998, which was the biggest portal dedicated to Quake 3 Arena on the polish side of the internet. Nowadays, the traffic there is insignificant, but it had a great run and a very vibrant community back in the golden days.
I inherited its original PHP codebase like 12-13 years ago, and over 10 years ago, I decided to rewrite it to Rails. So it was my first more significant, serious Rails project - which real users and actual data.
What’s the takeaway from the maintenance point of view after all those years? I would say test suite, automation (CI/CD), and being aware of dependencies graph.
Test suite, CI, and automation
Code can be terrible, but you should be more or less good if you have a good safety net - a balanced mix of unit and feature specs. Then, touching pretty lousy code is not scary, and a quick feedback loop thanks to the CI will tell you if you broke something. So you can take small steps (commits) and slowly get to the point you’re heading. I often thanked my younger self for investing in higher levels specs.
Upgrading Ruby is another story - nowadays, I usually go with docker - it’s just easier to build a stable environment with all the dependencies. A good automation process (part of a continuous deployment pipeline) running on any CI service again can make it more or less frictionless.
We often think of dependencies but forget about the dependencies of those dependencies. I prefer to upgrade six different lightweight gems than one that brings more implicit dependencies; and those are often quite tricky. Suddenly you can’t upgrade your dependency A, because B, relies on C, C relies on D, and D depends on a particular version of A.
Nowadays, I’m also cautious with libraries that hook too deply into the framework - such gems give you splendid power, but the long-term cost might be too high. I have some “trusted” libs that survived major Rails upgraded through versions 2 to 7, but I take everything else with a reasonable grain of salt.
Overall, as per usual, I don’t think there is any secret sauce here. Take small steps: upgrade a single dependency once a week instead of upgrading everything at once every year, refactor two lines every second day instead of rewriting half of trying to rewrite half of the codebase. Take care of the test suite (because it’s also code). Ensure your CI/CD pipeline runs smoothly (that includes eliminating flaky specs). And accept that some less-than-ideal code can just be left alone.