I’ve received an increasing number of requests for work on existing Rails projects lately. As Rails nears its fifth birthday and third major revision, the framework has existed long enough that a round of applications started in the earlier days are coming up major additions or revisions. Five years is also long enough for the original developer(s) to have moved on, for the application’s needs to expand faster than the current development team can handle them, or for an update the latest version of Rails to offer a tempting increase in reliability and decrease in LOC count.
Regardless of why I’m being approached, there are some questions I pose before accepting any work on an existing project. I want to share them to help people avoid some of the nasty time-stealing surprises I’ve had. Most of these questions are best answered by actually checking out the code and getting a sense of it yourself. Ask if you can have checkout rights (which often involves signing an NDA) before bidding on work. If you can’t get access there are some ways you can glean the information with properly worded communication.
1. Does the project have sufficient test coverage?
The specific testing paradigm (TestUnit vs. RSpec vs. something else) isn’t important but having tests is. An untested project means you’ll spend more time figuring out how the developers intended the project to work and may face some nasty surprises when unexpectedly poor architecture choices are discovered (possibly increasing the amount of time the project takes).
An untested application also has a signifincalty higher chance of breaking when you touch it. If the project is untested, are you willing to go everywhere in the application, trying every action manually with a variety of data to make sure your changes didn’t break process you don’t know about? Tests give you the confidence to boldly make changes.
If the original programmer(s) are around you can ask them to write tests or suggest that your first task on the job is to pair program with them and write tests. If the original folks are gone and can’t be contracted to help with testing, think very carefully about accepting the project.
The easiest way to determine test coverage is to run the tests yourself and use tools like Rcov. If the client won’t give you access to the full project, ask them to send along the largest test file and the smallest test file. The largest should cover a variety of concepts, or one a single concept in a variety of situations. The smallest should do more than assert that true is, in fact, true.
If there are no tests and you still want to take on the project make sure the client is comfortable paying the additional costs associated with learning the application and writing tests to cover the existing code first (before any new code/features can be added). Inform them ahead of time that despite your best efforts the application will break in unexpected ways because there isn’t an existing description of those expectations. These setbacks should be payed for by the client.
Most potential clients balk when I tell them their untested application might burn through the entire budget just arriving at the point where new development becomes possible. I usually let these jobs pass me by. After-the-fact test writing isn’t enjoyable even if the client is willing to pay for it; coding a project where breaking existing features is a constant worry is even less enjoyable.
2. Does the project strictly follow the MVC and REST patterns?
These two pattens form the essential core of Rails. In addition to being a good a way of organizing an application these two pattens are virtually expected by the framework. This wasn’t always strictly the case in earlier versions of Rails. People who develop Rails and people who develop using Rails were still hashing out how these patterns should be expressed in ruby code (remember Jamis Buck’s Skinny Controller, Fat Model article? What was a good practice in 2006 is a required pattern now).
In Rails 2+ deviating too far from these patterns means you’ll expend a lot of energy fighting the framework. You can easily pick out a Rails developer who doesn’t like or understand MVC and REST. Be wary if their routes.rb isn’t much more than map.connect ':controller/:action/:id', their actions are named about, dashboard, contact, save, js_save, and those actions contain a lot of data manipulation.
In applications structured this way expect a sizable portion of your task to be rearchitecting.
If you’re fortunate enough to inherit a well structured MVC/REST application you’ll find that the task of upgrading to new versions of Rails is easier and understanding how the various parts of the application are pieced together will feel natural.
3. Which version of Rails does the project use?
Rails 2.2 is a vastly different beast than Rails 1.0 was (and Merb 2/Rails 3 will be even more so). The framework has eliminated a lot of work that was previously done by the developer. Routing is more robust, the REST pattern is better integrated, models have an improved DSL for describing and manipulating complex data relations, and there is an amazing ecosystem of third party libraries that make development highly enjoyable.
If you’re looking at a Rails 1 application, speak frankly with clients about the advantages of new versions of Rails. Converting to Rails 2+ slows down development initially but you’ll reap the benefits later in the project. Of course, if the application also does not follow MVC/REST (point 2) and isn’t tested (point 1), converting to later version of Rails will be a major undertaking (one that the client may not be willing to pay for).
You’ll know which version of Rails is being used because it will be frozen into /vendor or declared as RAILS_GEM_VERSION in /config/environment.rb
4. How is deployment managed?
Eventually clients want their application updated in a production environment. Deploying Rails has changed as wildly as the framework itself - and at an equally rapid pace. Updating an older deployment structure to a newer one is much easier if deployment is automated. If the deployment instructions start with “first ftp the project to each box, then ssh in and run the command…” or don’t exist at all, you’ll need to manually examine how the production machines are configured and, hopefully, work with a previous developer to avoid missing any steps.
Right now I’m following most people’s suggestion of deploying via mod_rails unless there is some specific reason apache won’t work. This may involved changing hosting companies and porting over persisted data.
I’m taking the job. Now what?
If you’re lucky, you’ll be asked to work on a well tested, documented, properly modeled, MVC/RESTful Rails 2.2 app that just needs a little love. For those unlucky souls approaching a less than ideal existing project: think carefully about how you will manage your client’s expectation. Clients think the money they paid for existing code is an investment already made and set in stone.
They won’t want to hear that it took you 15 hours to “just add a button” because you had to follow someone’s convoluted logic down the garden path. They especially won’t want to hear that it took 30 hours to add the button because the apparatus behind that button was such a Rube Goldberg-esque collection of chicken wire and plaster that you had to rewrite a lot of code. (We once cleverly refactored away about 10% of own code. This upset the CEO who asked us to send her the old code so she could keep it: for her, more code == a product that did more. She couldn’t recognize that a drop in LOC wasn’t matched by an equal percentage of loss in function).
Remind your clients that web applications are complex systems. When the requirements of a complex system change, sometimes fundamental implementation changes are needed. When you add a second story to your house, sometimes you need to update the foundation. The original foundation isn’t broken, it just won’t support the weight of the extra space. Keeping the material from the current roof to put back on can be more costly than just building a new one (and a new roof can have better insulation to help cut energy costs because building technology has improved since Roof version 1.0).