This guide represents my current best understanding of how to get productive with an existing Ruby on Rails app. I currently work in a consulting / contracting role on various web development projects, and this general process would work with other frameworks similarly. I thought about the process that I go through (or would like to go through) when starting to work on an existing Ruby on Rails project, and wrote down those thoughts. Afterward, it seemed valuable enough to clean up a little and publish.
In this post I won’t cover getting a legacy project’s tests up to snuff. I also will not cover taking an existing Rails project and upgrading it to the latest version of Rails. There are existing resources that already cover this.
So what will I cover?
Let’s pretend you are starting to work on a Rails project today and this isn’t your first Rails rodeo. However, the project that you are starting on has been in development or on production for months or years. How do you quickly get up to speed to make a contribution, and how do you not break everything in the process?
This project could have been unmaintained for a little while. You could be joining up with an existing team. It could have been outsourced and in disrepair. It could have been insourced and in disrepair. :) It could be well-tested or have no test coverage. But someone, somewhere, cares about this project, or you wouldn’t be working on it. Where do you get started?
Overview
The first thing to do is to understand what the project does at a high level. This might seem obvious, but still important. Questions to answer include:
- What value does this project have to the business that owns it?
- What is the primary functionality of this project?
- Where does this fit in with the company’s other projects / products?
- Who uses this app on a daily basis?
- How many people use this app on a daily basis?
- What is the history of the project development?
- If the old developers are still available, can you contact them?
Once you have this information, you have a better understanding of the context of the project and those things most important to keep running.
Get Access
You can’t get very far without having access to anything important. You’ll eventually need access to:
- source control (Github)
- project management (Trello, Jira)
- team communication tools (Slack, Teams, Zoom)
- supplementary docs (Google Docs, Notion, wiki, Miro)
- deployment credentials (Heroku)
- error notifications (Airbrake, Exceptional, Honeybadger)
- storage credentials (AWS)
- end user message apps (Helpscout)
- performance monitoring (New Relic)
- continuous integration (GitHub Actions, Jenkins)
I would generally send out one email to whoever should have most of these and see if you can get credentials or get your account added. I would then make a document that has all of these services in one list, and then whenever you onboard a new developer you can add them more quickly the next time.
Reading the Fine Manual
After understanding the project, the next thing to do is to look at the clues previous developers have given you. Sometimes you get the dreaded Welcome to Rails documentation, but often your predecessors or teammates have left a breadcrumb trail of how to get up and running on the app.
Read through the README
. Check out the doc
folder, which I’ve seen contain very detailed and useful information. If the project is hosted on Github or the like, check to see if there is a wiki. Also, look at the Gemfile
. This will give you a sense of the parts of the application and can be an effective way to date apps. Look for the following answers:
Does the documentation seem up to date? Compare the documentation to the Gemfile and see which elements are discussed and which aren’t.
Are there any gotchas or other things to watch out for? Maybe the project shouldn’t be deployed more than three times an hour or the app workers will cause too many external auth requests. (Side note: as a developer, if you know things like this about your project, document it so the next person knows to watch out for this!)
What versions of Ruby/Rails is the app using? If it isn’t yet documented in code, I use .ruby-version
to hold the version (say, 2.0.0p123) and .ruby-gemset
to hold an appropriately named gemset. RVM and other ruby environment managers look at these files and can automatically switch to the right Ruby version and gemset when you change to the project directory. This encapsulates your project environment better and ensures that you have only the things you expect actually accessible.
What database is the app using? You’ll need to have this installed, with the right version. If the database version is not clearly specified, look at what production is running. Note any differences in database versions between environments.
What external services does the app interact with? You may need to be aware of these and have at least development credentials for it to work correctly.
Get a local development server running
The next goal is to get an environment where you can see the results of changes that you make to the app. Ideally there would be a project setup script you can run or a Vagrant environment that you can run in to do all of this setup for you. However, I have found that this is not typically the case. You may need to:
- Copy the example database configuration and make any necessary changes
cp config/database.yml.example config/database.yml
- Create the databases, run migrations from scratch, look at the seeds and run them
- See what server is used in production by looking at
Procfile
Document any additional dependencies required or gotchas. Also, note any differences between running the migrations from scratch and what your final database schema looks like. Sometimes there are database scripts that were not idempotent, run correctly, or were removed somewhere along the way. You may need to fix some migrations if they reference classes that have been renamed and have not been run since that happened.
Hopefully you can get a development server up and running and run through the app a bit. At this point you can also test out running workers and other things if they won’t impact other environments.
Get the tests running
Get the tests running locally if they are somewhat reasonably maintained. If there are no tests or the tests are all or mostly failing, this is something to be aware of and you may reconsider your project and/or life choices. Some failing tests may point to actual problems in your environment. More likely, the tests themselves were not maintained properly or are not deterministic. Generally if the tests at least pass at the file level, then you can proceed. Document any test environment setup that was needed to get things going.
If the tests are non-deterministic, consider introducing database_cleaner and timecop, and potentially doing other analysis.
Next, understand the code coverage. I personally have room to grow in this area, but saying “All of the tests pass, I have no idea why staging is broken!” is a poor substitute to understanding that the tests are weak in an area and beefing them up or doing manual testing to prevent regressions.
Install simplecov and run against it the app.
Save this somewhere for future analysis / comparison. This is useful for being able to say, “we increased the code coverage of the app by 10% in the past three months.”
Understanding (or creating) the project workflow
Next, get one change into production through any other gates that are necessary (staging, demo, etc.). This could ideally be a minor change like a comment in one of the view files so you can see that it went through correctly without impacting any end user behavior. Pushing up a change at this point ensures that you can push up changes to the codebase so you can deliver the value that you create. This also ferrets out any issues you might have with deploying code sooner than later (insufficient privileges, production errors on push, etc.)
Figure out how you should interact with the current codebase to get your changes in. What is the merge workflow like for the project? Is it documented somewhere? Perhaps you should use pull requests or feature branches. Basically you don’t want to stomp on other people’s commits if the project is currently under active development, and want to put commits through a consistent code review.
What is the deployment workflow like for the project?
- What environments are this application deployed to, and who uses them?
- Where are the backups, if any, for each environment?? :)
- Know branches to deploy from, where it deploys to, git remotes, etc.
- Do I have any needed deployment keys or credentials?
- How is the performance monitored on each environment?
Push your minor change to intermediate environments and then production. When you see your change on production, you are done with basic setup of the app. Congratulations!
Intermission
The more this process is documented / automated, the less time will be required to onboard new developers. So be sure to document anything that you needed to do to set it up.
After getting the code running and successfully deployed, you may wish to make a few more improvements, which I’ll outline below.
Understand the application code
To get a better understanding of what is going on under the hood, make sure you review most of the code to see where the functionality lives. You can run rake stats
to get a sense of how many lines of code are in each major section of the app.
I recommend running the Rails ERD tool or something similar (RubyMine comes to mind) to visualize the data model to get an understanding of what models interact with other models. Sometimes fairly basic data model problems can be uncovered just by looking at the output of this project.
Run through the app code quickly, starting with the models and lib files, moving to controllers, and finishing with views and JavaScript/CoffeeScript files (viewing helpers as needed.)
Are all of the gems in the Gemfile documented and used? Be sure to understand how all of the gems are or might be used. If project startup is extremely slow, consider using bumbler to get the project load times down by optimizing which gems are actually required and which ones can be loaded dynamically by your app.
Improve your working environment
Get quick iteration times by installing and using Zeus or the now-more-official-than-Zeus spring. This should speed up your testing iteration by a large factor.
Get faster testing times if the suite takes over ten minutes wall time to run. Install and get parallel_tests working as one way to solve this problem.
High level audit
Are there any project security issues to be aware of? You can add great value with fairly low effort by ensuring that the project is safe from known or silly security vulnerabilities.
Upgrade Rails
First, ensure the project is running on the latest point release of the current minor that you are on, with upgrading to a later Rails minor or major release if that is feasible. Upgrading a minor or major version is an undertaking that may take days or weeks, so you should probably note this and discuss with your client.
Example: if your project is running 3.2.12 (major 3, minor 2, point 12), you should upgrade to the latest in the 3.2.x line since point releases are likely backward compatible. Thereafter, look to upgrade to the highest minor release of the 3.x.y (if not there already), until you get to a major release (so 3.2.latest to 4.0.latest to 4.1.latest).
The theory is that point releases are most likely to contain security or other bug fixes, and upgrading a minor or major revision might take more work but is useful for keeping the app up to date. Again, I won’t cover this process in more detail.
Upgrade gems
Grab bundler-audit and run bundle-audit update
and bundle-audit
to see what known security problems are present in the current gemset. You should upgrade these at least minimally to fix the issue, and hopefully the app will have tests that break if the gem functionality changes in a breaking way.
While you are at it, you can get a sense of how outdated all of the gems are and how much time it would take to upgrade the app to use the latest versions of those gems. This is useful for security, increased functionality, and usually bug fixes and speed improvements. Run bundle outdated
to see which gems are behind their current latest revision on RubyGems.
Other security issues
Check for SQL injection attacks by at least looking through the app and searching for connection
or execute
and inspecting these for potentially user-supplied values.
Look for other basic issues as explained in the following guides:
Supporting infrastructure and services
You can add a lot of value by ensuring that you have the following services connected to your app:
- error reporting service (Honeybadger, etc.)
- performance monitoring (New Relic)
- continuous integration server (Jenkins, etc.)
These ensure that your app is running properly and that you can detect when things are not running well. The CI server will ensure that tests are running consistently and deterministically. Typically you can get hosted solutions for all of these problems which will save the project time and therefore money.
What did I miss?
Some people reading this post thought that there were a few more things that you might consider to help your understanding of the codebase.
Pro blogger and Ruby enthusiast Matt Swanson suggests the following tools:
Indianapolis Ruby developer and Wicked PDF expert David Jones suggests looking at your codebase with some tools / services to get a sense of what is especially tricky:
What else did I miss or that you find especially helpful? Thanks!
Original tweet: