After nearly seven years of working primarily in one Rails codebase in my day job, I’ve recently transitioned to a handful of other Rails applications, all written by different teams, at different times, and with different understandings of the framework. And that code I started working with seven years ago had been in active development for several years before I arrived, so it had its own long, decorated history.
Chances are that if you start a new Rails job, it’s going to be to work with an established application with a lot of history, too. The label thrown around for such applications is often legacy. That term means different things to different people, but for this article, we’ll focus on one characteristic: context. Legacy software projects are those for which a current developer lacks full knowledge of its context, yet the software is still actively used and relied upon. (I did not come up with that loose definition, but I’m having trouble tracking down who did–if you know, please let me know.)
Working with legacy codebases can be daunting, especially that first time you clone a repository down to your development workstation and fire up your editor. But as already mentioned, legacy applications still have users relying on them to perform. They have lots to teach us as developers, and give us opportunities to make improvements where we may not have been able to with a pristine application. Knowing how to work with these codebases is a valuable skill.
In this article, I’ll share where I look for clues about the application’s purpose, its implementation, and how to be productive as its caregiver going forward.
This might seem obvious, but the first place I’ll check in the new (to me) application is its README, asking the following questions:
Does it even have one? All the applications I recently picked up have README files, but I’ve run into some in the past that either have none, or have the default file generated by the
rails new command.
If your application doesn’t have a README, your first task is to create one, and fill in the bits of information you do know about the application so far. For now, that might only be its name and a little bit of its business purpose, but that’s fine! It’s more than what was there before.
Does it include development setup instructions? Just note whether or not it does, for now. They may or may not be accurate. Depending on if the application has been maintained recently or not, you may need to revise whatever’s here. More on this a little later.
You can learn a lot about what you’ve gotten yourself into by looking at a Rails application’s Gemfile. It’s the next file I’ll check in the codebase, looking for the following:
Which Rails version does the app use? Luckily, all the applications I just picked up use version 4.0 or newer. Version 3.x Rails apps are still out there doing their thing, too. Have you been requested to work with something even older than that?
Which Ruby version was the application built on? This detail is sometimes included in the Gemfile, especially if the application is deployed to Heroku. If you don’t see it, check for a
.ruby-version file. If that’s missing as well, you may be able to track down more information by looking at the application’s production environment. Which Ruby is in use on the server hosting the software?
What are the app’s dependencies? Some may be familiar, some not. I like to search RubyGems for those unfamiliar ones, to get a sense of why they’re included, if they’re still in active development, and general popularity. I do not judge a given gem’s merits on either of those last two attributes, but they’re still good to know.
Does the application use a typical authentication library? For Rails, this is often Devise. But it could be OmniAuth, or Clearance, or a handful of other gems. A lack of one of these included as a dependency may be a sign that the application uses a custom mechanism for authentication, or has none at all. Neither of these is necessarily a red flag, but it is something to watch for moving forward into development. Consider performing a security audit to ensure login isn’t susceptible to attacks, early on.
If you don’t even see a Gemfile, you may be working with a really old application that hasn’t gotten any attention in some time. Bundler support wasn’t formally added to Rails until version 3.0. Your life may have just become a lot more interesting. You might need to do some extra work to get older Rubies and dependencies up and running for development.
Next, I’ll start looking at things that make the application unique. While it’s tempting to start diving deep here, I like to continue at a high level for now. If the app follows Rails conventions, I can learn a lot by exploring:
config/routes.rb: Does the application have complex routes with heavy nesting? Does it lean on RESTful resources? Does it have lots of controllers, or fewer, potentially overworked controllers?
rails routes: An alternate view of the app’s routes, their structure, and any helper methods available to make accessing them easier.
app/models: How many models are present in this directory? Do their names make sense in the context of the application’s business purpose, as you understand it at this point? I don’t dig into the files themselves yet, but I do try to get a rough count of how many models there are, and rough file sizes for each–theoretically, more complex applications have more models. Large files may likely be the application’s god objects. Make notes for future exploration!
rails stats: Finally, we can get a nice, broad strokes report of the application structure by checking its stats and asking questions like, how many lines of code are in models, relative to controllers, relative to libraries? Where’s the test coverage–in low level unit tests, or high level system or integration tests, or somewhere in the middle? (More on test coverage later.) And how many methods per class (
M/C) and lines of code per method (
LOC/M) are there on average? While the numbers represent averages, they tend to help show how past developers structured code, so you can begin knowing where to look for more details.
The application’s database schema often sheds additional light on its complexity. Take a couple of minutes to skim
db/schema.rb (assuming it’s a typical Active Record-based app) to get a sense of its table structure. Does it have lots of tables, or just a few? Do any tables look like they’re doing too much? Does the application leverage indexing or other database layer-specific features?
As long as you’re in the directory, take a look to see if
db/seeds.rb exists, and has anything in it beyond the commented sample code provided by Rails. Does it look like it’ll help you set up your development environment later? If not, do you know what’ll be involved in getting accounts or other required data in place to use the app locally?
On initial exploration, the keys to watch for are:
Which frameworks are used?
Where are the assets stored–in
How are assets compiled for production (or are they at all)? For a long time, this has typically been handled at least in part by the asset pipeline in Rails, but be prepared for other approaches in both really old applications and more recent ones.
A Rails application isn’t just Ruby code alone. There are external factors like the database, perhaps a separate key/value store for things like caching and background process management, mail delivery, file storage, image manipulation, and the list goes on. Can these be gleaned from the README, or the Gemfile, or container orchestration configuration, or some other documentation? Do they use default settings, or is customization required? Do developers manage these externalities, or will you need to reach out to another team for support?
There are also external services interfaced via APIs to consider. What are these services? Are there credentials required to access them? How are they configured? Are they different between development, testing, and production environments? Are there rate limits or other restrictions to be aware of as a developer? Not only can awareness of these factors help you get started coding on the new-to-you project, it can also help avoid taking out production systems by accident!
I’m a big fan of learning about a project through its version control history. When was its first commit? (
git log -p --reverse). When was its last commit? (
git log -p). How are commits structured–as small, atomic changes, or sweeping overhauls? Do commits include detailed messages describing the need and intent behind the code change?
If you’re lucky, your inherited codebase’s previous developers practiced good version control hygiene, and the application’s history tells a story that guides future work. If not, the history can still help identify past contributors, and key points in the project’s history such as major upgrades or other big changes. If the project’s hosted on GitHub or another platform that provides commit analytics, refer to them as well for additional insight.
This is a bit of a reach for most Ruby projects, especially a Rails application, but I do like to see if previous developers have written any documentation for the code, especially if it’s light on unit testing. Before version 5, Rails included a Rake task (
rake doc:app) to output docs into HTML. This feature has since been removed, but can be added back with the sdoc gem.
As an aside, I’ve begun adding documentation to my applications as I learn about them. This is especially useful for apps with models that may make perfect sense to people who’ve been thinking in the domain they represent for years, but are totally foreign to newcomers. It helps solidify my understanding of the application, and provides information for future developers.
With all the background knowledge in place, let’s see if it’s enough to get started working on this new-to-you codebase. Can you get the app to boot for development? I’ve run into problems with apps on really old versions of Ruby running into issues installing gems, or even Ruby itself, on newer versions of my development computer’s operating system and system libraries. If that’s the case for you, consider advocating a Ruby upgrade for you app, in the name of security and enabling feature enhancements. In the meantime, you could also set up a development environment using Docker, to help avoid these installation issues.
If there were setup instructions provided, are they correct, or did you have to make adjustments? If you did, create a new git branch with any changes you had to make to these instructions–even if you think nobody else is ever going to work with the code.
If you can get the Rails console to boot, and you can see your app running on localhost, congratulations! You’re ready to work with your legacy application.
For the apps I’m working with, working test suites have been more like nice bonuses than hard requirements. But it’s worth the time to check to see the test suite’s status. Some things to check:
What framework is the suite written in–MiniTest, RSpec, Test::Unit, Cucumber, or something else?
Does the suite run? And if it does, does it pass? Is it fast or slow?
Where’s the coverage? You may get a sense of this by looking at the file structure in the
features directories. Run
rake stats if you haven’t already, to get a rough idea. While you’re at it, spot-check to see whether the files actually have any useful tests in them, or if they’re boilerplate created by Rails generators–sadly, I have seen this when inheriting old codebases in the past, but luckily not this go-around.
I hope this advice helps make your next foray into a legacy Rails codebase less scary. By digging into the areas I’ve listed here, you can begin to understand the application and its history, and equip yourself to make enhancements and upgrades as needed. Thank you for reading!
If you liked my series on practical advice for adding reliable tests to your Rails apps, check out the expanded ebook version. Lots of additional, exclusive content and a complete sample Rails application.
Ruby on Rails news and tips, and other ideas and surprises from Aaron at Everyday Rails. Delivered to your inbox on no particular set schedule.