How We Upgraded an Obsolete Ruby on Rails Application with Lots of Legacy Code (part 1)
Captain’s log, stardate d604.y36/AB
As a development consultancy, we deal with a whole lot of projects: static websites, web platforms, intranets, mobile apps, etc. Some are bigger, and some are smaller, but all of them require maintenance.
This post is the first one of a series where we break down how to correctly upgrade a Ruby on Rails application.
In this first part, I am going to introduce you to the project and how we planned the upgrade minimising risks because the project is already live.
At MarsBased, we usually prefer to develop projects from scratch but sometimes we inherit projects with legacy code. Legacy code is code written by someone else. In web development, it is often referred to as such when it was written by a previous provider, or by someone that left your company a long time ago.
Cons of working with legacy code are obvious:
- The learning curve of that code is steep.
- The code might not be documented at all.
- The code might not be tested at all either.
- The existing code might not be compatible with the new code.
- The code might not follow your style guide
- The code is not maintainable in the long run.
- There might be security issues caused by unmaintained third-party libraries.
The previous enumeration is of course not exhaustive. Obsolete applications must be taken care of.
Keeping outdated apps — apps with lots of legacy code — is like dealing with a timebomb. Even if the app runs smoothly and seems to work perfectly, it will eventually cause trouble.
Security, performance and maintenance issues will arise when you least expect it, not to mention that legacy code will not allow you to implement new features and improvements that come with the most recent versions of your programming languages, libraries, and frameworks.
Let’s study our case
In February last year, we welcomed the opportunity to start working on the maintenance of a national online platform for digital newsletters and magazines.
Their previous provider had developed a custom CMS built on top of Ruby on Rails, which had not been maintained for a long time. Therefore, the project was a monolithic & outdated solution someone had to take care of when the previous provider closed the company and left our customer without services.
We agreed to take care of the project: we would be working on the maintenance of the project while we developed some small features to keep up with their business necessities. However, as a background task, we would study how to upgrade the platform to current versions, as rewriting the platform from scratch was out of the question.
After almost a year of working on the project, we decided that the best decision was to upgrade it to the current versions of Ruby, Rails and its libraries.
If this project had been updated periodically, the platform would have never become so obsolete (and we would have never written this post!). Among others, these were the biggest problems of such an obsolete platform:
- The application’s performance was at an all-time low.
- We struggled to find documentation for the versions used in the platform.
- Developing new features without breaking anything was too complex.
- The cost of maintenance increased with time.
- Some gems were not compatible with each other.
- The application’s autoreload was not working, due to the excessive overriding in the /lib folder.
To define an upgrade strategy, we need to understand the software versions and their particularities, requirements and dependencies. In our project we had:
- Ruby 1.8.7 (2012).
- Rails 2.3.18 (2013).
- A platform built upon an unmaintained CMS through some git submodules.
- Too much overriding on the /lib folder over /vendor submodules.
- Abusive usage of alias method chain and metaprogramming, thus difficulting the data flow tracking and bug fixing.
- Outdated gems due to the Ruby and Rails versions used in the project.
The first step you should take when migrating from one version to the other is to verify the test coverage of the project, in order to have a safety net. In this specific project, the test coverage was only 10%, and 95% of the tests were failing.
This is a very common scenario in most long-running projects that have been maintained and/or developed by many contributors. Because of that, we couldn’t follow a TDD-based project approach, as we needed to adhere to some deadlines required by the nature of the business.
These are the steps that we followed:
- Reduced the overall complexity of the codebase.
- Planned a Ruby upgrade from version 1.8.7 to 1.9.3 before attempting to upgrade Rails.
- Planned a Rails upgrade from Rails 2.3.18 to Rails 3.0
- Then, upgraded Rails from version 3.0 to 3.2
- Upgraded all third-party gems as most of them are compatible with Rails v3.2
- Finally, upgraded Rails again from version 3.2 to 4.x
We followed these steps both for the upgrade of the platform and for the CMS itself as two separate processes.
How will the situation change when we upgrade to the current versions?
These are only some of the advantages of updating this obsolete app, but pretty much applies to any obsolete Ruby on Rails app:
- All of the above problems will be solved.
- Some security problems older versions of Ruby and Rails had will disappear.
- Performance of the platform will increase.
- We will be able to take advantage of the new Ruby features found in the newest versions.
- We will give a huge boost to developers happiness.
- We will be able to benefit from using the Rails Asset Pipeline to better cache our assets and improve the management of third-party frontend libraries.
- This will allow us to use friendlier implementations of Active Model and Active Record, among others, found in the newest Rails versions.
The process of dealing with such a monolithic solution takes a lot of time and dedication. Although most of the work is done now, we are still working on this upgrade process slowly.
As mentioned above, we just covered the introduction & strategy of this upgrade. We will be blogging about the Ruby and Rails upgrades in the following entries.
It is also worth noting that this upgrade requires a deep knowledge of the installed versions of Ruby and Rails. Before doing this we had been working on the platform for over a year!
Here’re a couple of important things you need to keep in mind when performing an upgrade of this nature:
- It is important to split this process in small iterations and integrate them in the application step by step, lest you overcomplicate the code and make it too difficult to fix bugs.
- You must absolutely neither optimise nor refactor the existing code because you might be introducing new bugs in the app’s code.
And that’s a wrap!
Have you ever dealt with a similar situation? Let us know in the comments section below! Also, if you need help with a challenge of such difficulty, you know you can drop us a line and we’ll do our best to help you out!
Originally published at marsbased.com on June 6, 2016.