Legacy Rails: Silently Judging You

I had to come up with a clever title because this article is about legacy Rails applications and I know that you might fall asleep by the third paragraph. Boooooring... You probably want to read about that new JavaScript framework that came out (I love that this sentence will always be true, it doesn't matter when you read this)

If you have been working with Rails for a few years, you have seen your fair share of shiny new applications, well-maintained and poorly-maintained legacy applications. This post is about Legacy Rails applications

So let's start judging them! Are they in good shape? Should we upgrade them to a more recent version of Rails? Should we re-write them?

Judging You

How can you judge the quality of a Rails application? Here is a process that works for us.

Dependencies Judgment

A good way to get started is to find out how outdated an application really is. You can easily do this with Bundler's bundle outdated:

$ bundle outdated
Fetching gem metadata from https://rubygems.org/..........
Resolving dependencies.......................................................................................................................................................

Outdated gems included in the bundle:
  * actionmailer (newest 5.2.2, installed 3.2.22.5)
  * actionpack (newest 5.2.2, installed 3.2.22.5)
  * activemerchant (newest 1.90.0, installed 1.53.0) in groups "default"
  * activemodel (newest 5.2.2, installed 3.2.22.5)
  ...
  * tilt (newest 2.0.9, installed 1.4.1)
  * todonotes (newest 0.2.2, installed 0.1.0)
  * treetop (newest 1.6.10, installed 1.4.15)
  * tzinfo (newest 2.0.0, installed 0.3.52)
  * uglifier (newest 4.1.20, installed 1.2.4) in groups "assets"
  * unicorn (newest 5.4.1, installed 4.8.3) in groups "default"
  * uniform_notifier (newest 1.12.1, installed 1.4.0)
  * whenever (newest 0.10.0, installed 0.8.2) in groups "default"
  * wicked_pdf (newest 1.1.0, installed 0.9.10) in groups "default"

Using this information, you can review the Gemfile and improve it. If you find dependencies that are declared like this:

gem 'rails', '4.2.7'

You could improve them like this:

gem 'rails', '~> 4.2.7'

Next time you run bundler update it will jump to the latest patch version. Your application doesn't have to be tied to Rails version 4.2.7, it is safe to assume that it would work fine using version 4.2.11.1. Using the pessimistic operator (~>) you make sure that you get the latest security patches and deprecation warnings.

You can try this with all dependencies that are tied to a specific version. Sure, there might be a very good reason for that dependency to be that concrete, but probably not.

You can read more about the pessimistic operator over here: https://guides.rubygems.org/patterns/#pessimistic-version-constraint

Known Vulnerabilities Judgment

Another handy tool for analyzing your Gemfile.lock is bundler-audit. You can quickly install and run it to find known vulnerabilities associated with the gems you are running in production.

If you want to do a quick assessment, you can use our free tool: https://audit.fastruby.io

Later on you can share the security report with the rest of the team. For example: https://audit.fastruby.io/gemfiles/AA70B955

Database Judgment

How many tables are in your db/schema.rb? How many models are in your app/models directory? Does it have 10 tables? 200 tables? This is a quick indicator that could tell you a little bit about the complexity of the system.

You can quickly get a sense of the size of the models (and more!) using rake stats

$ rake stats
+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          |  2941 |  2388 |      49 |     416 |   8 |     3 |
| Helpers              |  1186 |   952 |       8 |     183 |  22 |     3 |
| Jobs                 |  1398 |  1132 |      58 |     120 |   2 |     7 |
| Models               |  4911 |  3869 |      45 |     712 |  15 |     3 |
| Mailers              |   342 |   269 |       6 |      41 |   6 |     4 |
| Javascripts          |   212 |   161 |       0 |      24 |   0 |     4 |
| Libraries            |  1332 |   958 |       7 |      96 |  13 |     7 |
| Task specs           |    34 |    28 |       0 |       0 |   0 |     0 |
| Mailer specs         |  1708 |  1351 |       4 |      28 |   7 |    46 |
| Model specs          | 14254 | 11580 |       1 |       2 |   2 |  5788 |
| Presenter specs      |   122 |   104 |       2 |       4 |   2 |    24 |
| Request specs        |  1877 |  1508 |       0 |       1 |   0 |  1506 |
| Lib specs            |   603 |   504 |       0 |       0 |   0 |     0 |
| Validator specs      |   161 |   126 |       0 |       2 |   0 |    61 |
| Routing specs        |   630 |   490 |       0 |       0 |   0 |     0 |
| Job specs            |  3997 |  3282 |       1 |      14 |  14 |   232 |
| Controller specs     | 13949 | 11479 |       0 |      53 |   0 |   214 |
| Helper specs         |  2847 |  2302 |       0 |       3 |   0 |   765 |
| Cucumber features    |  3258 |  2533 |       4 |      28 |   7 |    88 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                | 55762 | 45016 |     185 |    1727 |   9 |    24 |
+----------------------+-------+-------+---------+---------+-----+-------+
  Code LOC: 9729     Test LOC: 35287     Code to Test Ratio: 1:3.6

You could take a few minutes to draw the ERD associated with the database. If the application has more than 20 tables, you could use something like rails-erd to quickly generate an Entity Relationship Diagram.

Static Code Analysis

You can use tools like flay, flog, and heckle to do static code analysis and get a sense of what parts of the application will cause most of the maintenance pain. More info over here: Ruby Sadist (NSFW)

We prefer CodeClimate for this. You can get a quick sense of the maintainability level of your application:

Sample Code Climate Report

You can filter all the existing issues to get a sense of the most critical issues:

Sample Code Climate Report

Code Climate will give you an idea of the complexity of the application.

There are certainly many free, open source alternatives, which you can find over here: https://github.com/metricfu/metric_fu/wiki/Code-Tools

Setup Judgment

Does the application provide a well-maintained Dockerfile? Does it provide a ./bin/setup script? Do they run?

Setting up a new development environment should be as simple as:

./bin/setup`

Or:

docker-compose up

Or:

vagrant up

You get the point. It is a red flag when you need to pair with someone to get your development environment set up. You should count the times you run into unexpected issues. This will give you a good idea of the complexity of all environments.

Build Judgment

Is there a set of spec files? Does the build work in your environment? After you have set up your local environment, you should be able to run:

rake

I'm used to projects and gems using test or spec as the default Rake task, which has been the default task for Rails for a while: https://github.com/rails/rails/blob/5-2-stable/Rakefile#L21-L22

# Rakefile
desc "Run all tests by default"
task default: %w(test test:isolated)

It is a red flag when you run into unexpected issues when running the build. If some tests failed, why did they fail? How many random failures did you get? If you found many issues, why didn't ./bin/setup take care of it for you beforehand?

Coverage Judgment

Let's say your application is so well maintained that all previous steps worked flawlessly. bundle outdated returned no outdated dependencies; static code analysis reported an A+; you run ./bin/setup to install everything you needed; and the rake showed you a passing build. That is great!

How well covered is it? Does it cover more than 80% of your code? Does it cover 100% of the critical user path?

You can quickly get a sense of test coverage with SimpleCov. Simply add this to your Gemfile:

# Gemfile
gem 'simplecov', require: false, group: :test

Then add this to your test helper file:

if ENV['COVERAGE'] == 'true'
  require 'simplecov'
  SimpleCov.start 'rails'
end

Then bundle install and run the build (e.g. COVERAGE=true rake). You should get a report like this one:

SimpleCov Report

12 Factor Judgment

Yes, yes, we all know we should be following the twelve-factor methodology but some people missed the memo.

How many values are hardcoded in the configuration files? Are there more than 3 environments? How are environment variables managed? Are they using a config/secrets.yml file? Is config/database.yml checked in to the repository? If so, is it using environment variables? Are all dependencies clearly declared? How are they tracking exceptions in production? What does the time between deployments gap look like?

MVC Judgment

One of the best selling points of Rails is convention over configuration. You know where to find code for models, views, and controllers.

There are some applications out there that don't apply this pattern the right way. They may include queries in views, business logic in controllers, and have fat controllers.

It is a red flag if you do a quick code analysis that shows that there is spaghetti code in your views, controllers, or models which breaks the Rails' conventions.

Quick Performance Judgment

Do they use Skylight? NewRelic? Scout? What are the slowest and busiest requests? What are the main performance problems? Are the slowest requests related to the most complex files in the app?

Final Remarks

At this point you should have a good idea about your legacy Rails application:

Is it in good or bad shape?

You can quickly assign a score to each of the steps I mentioned and add them up. From 1 to 100, how maintainable is your app?

Time to judge with scores!