A Rails Application Factory

This is a post about creating fresh Rails applications quickly and maintaining them easily over time. The approach I take here could easily be applied outside of Rails to any software project.

My Philosophy of Friction

I like to think about productivity in terms of “friction”. My inner economist wants me to call this “marginal cost” but my inner consultant thinks “friction” will sell better. The principle I try to organize by is that things which you want yourself to do on a regular basis: exercise, eat well, churn out a shiny prototype in next to no time; should be quick and painless. You should try to reduce the friction between you and your desired outcomes. So, make it easy to eat well by stocking the kitchen with plenty of healthy, easy-to-prepare food. If you never go to the gym because there’s never any parking, or you have to make a tricky right turn to get into the car park (I live in Ireland, remember, where right turns are the tricky ones), this is friction and the more friction there is, the less likely you are to do something. Join a different gym, or make yourself consciously aware of the friction and make the decision to go to the gym anyway. Friction is nefarious since it works on our subconscious, sometimes just being aware of it can remove its influence, but staying aware of it is difficult.

Reducing friction for good things, and increasing friction for bad things (put those cookies on the TOP shelf), can be a simple and effective guideline for orienting your practices towards your goals. (But, please do remember that marginal thinking does not show you the entire picture. Somewhere you need to think about the sum of what you are doing, not just marginal increases or decreases. Friction is very useful as a short-term heuristic, though, and it’s also usually the right tool for predicting behaviour in other people, especially users of software. Check out Why We Buy by Paco Underhill, one of my all-time favourite books for its content and basically all about friction.)

So, this week I decided to reduce a few forms of friction in my programming life. To begin with, I wanted to improve my strategy for creating a fresh Rails application.

Creating A Fresh App

Of course, actually building a generic Rails application is very straightforward, you simply type:

rails example

and you will have a new directory called example which contains a skeleton Rails application. Very quickly, though, a skeleton Rails application isn’t going to be enough. Most developers will have a collection of plugins, rake tasks and other goodies which they want to install on every application. I head straight for rspec and active_scaffold. Also, I don’t have any use for public/index.html or the test/ directory so I will always delete these. The details don’t really matter, the point is that there is a series of repetitive tasks which we do whenever we set out to create a new Rails application.

My original approach to automating this was to write a shell script which went through the various steps for me:

# Usage: bash setup_rails projectname
rails $1
cd $1
ruby script/plugin install svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec
ruby script/plugin install svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec_on_rails
ruby script/generate rspec
ruby script/plugin install http://svn.caldersphere.net/svn/main/plugins/rspec_autotest
svn export http://activescaffold.googlecode.com/svn/trunk vendor/plugins/active_scaffold
svn export http://activescaffoldexport.googlecode.com/svn/trunk vendor/plugins/activescaffoldexport
svn export http://dev.agent.ie/svn/rails/generators/ajax_rspec_resource/ lib/generators/ajax_rspec_resource
ruby script/plugin install exception_notification

echo "About to drop and recreate development and test databases. Next password prompt is for mysql user password."
echo "drop database if exists $1_development;" | mysql -u root -p
echo "drop database if exists $1_test;" | mysql -u root -p
echo "create database $1_development;" | mysql -u root -p
echo "create database $1_test;" | mysql -u root -p

Wow, mysql and svn, that takes me back. ;-)

With this script, I could create a new Rails application in a few seconds and my favourite plugins would be installed and configured, databases would be set up and a custom generator installed and ready to go.

This was definitely better than nothing, but it had some problems. I would tend to have a spurt of starting a few new projects, and then there would be a lull of a few months where I was working on existing projects. By the time I went back to use this script again, things would have changed. Plugins would have moved or I would be using different ones. Or, I would forget I had written the script and find myself halfway through setting up an application manually before I remembered that, yes, I invented an easier way to do this once upon a time. But, the crucial flaw in the setup_rails.sh concept is that it does nothing to help you automate the maintenance of your Rails applications. I had automated the birth process, but not the much more onerous ongoing maintenance which is necessary for any piece of software with external dependencies.

So, finally, this morning I worked out a strategy which is going to solve all of these problems. There’s no script involved, just some really easy branching and merging using, in my case, the Bazaar version control system.

The strategy is:

  1. create a skeleton Rails application configured just the way I like it named template-rails
  2. each time I want to create a new Rails application, I bzr branch template-rails my_new_app. This creates a copy of the contents of template-rails in a new directory called my_new_app, and the my_new_app branch “remembers” that it originated from template-rails.
  3. each time I upgrade a plugin, tweak a rake task, write a new spec helper or anything else that I want to share amongst more than one application, I make this change to template-rails and then bring the changes into each application by just typing bzr merge and, later, committing the changes.

So, this covers both creating and maintaining applications in one easy step. Because I interact with version control on a daily basis, I’m not going to forget what my setup is, whereas I might easily forget about a six-month-old shell script tucked away in some bin/ directory.

Bazaar Tricks

I’m sure you could do this with any version control system that has good (low-friction!) support for branching and merging. I happen to be partial to bazaar because, coming from subversion, it took me 5 minutes to learn the basics but I’m still discovering wonderful little features that make my life so much easier. Just this morning, for example, I learned that if you bzr rm --new --keep, bazaar will remove whichever files you have just accidentally added to your repository without deleting them, they just go back to being “unknown” files. This is GREAT when you add several directories without adding, say, “.git” to your .bzrignore file. You can just undo the add, tell .bzr to ignore the pattern, then safely add just the files you want. When I use a plugin from github I git clone it and leave the git branch information intact. It doesn’t conflict with bzr and I can easily pull down changes.

A trick which is particularly useful for working with a template like I have been describing is described in Section 7.2.3 of the Bazaar user guide. While I would probably go into template-rails/ to pull down the latest version of a plugin, if I am editing something I have written myself like a rake task or a spec helper, chances are I’m going to be working on this and testing this in a live application. But, once I have upgraded or fixed the code, I probably want to bring this change back into my template so it can be shared with my other apps and any apps I create by branching this template in the future. Here is a nice little workflow assuming I start out in my example/ application directory:

cd ../template-rails
bzr merge --uncommitted example/
bzr diff
bzr commit . -m "Refactor matchers to derive description automatically from class name."
cd ../example
bzr merge
bzr commit . -m "Merged changes from template."

The merge --uncommitted example/ is just a fantastic little command which gets whatever changes you have made in the example/ branch but not yet committed (this does not know about “unknown” files, so if you create a new file you have to add it to the repository for it to show up here). So, you can happily develop and test in any of your branches and pulling the changes back to the template is a couple of quick commands away.

Sharing Code

A side effect of adopting a system like this is, because you have reduced the pain (or friction, or marginal cost) associated with sharing code between your applications, you are more likely to share code. You are more likely to write test helpers, custom rake tasks and your own plugins. Sharing code between your applications is a great way to improve it and the lessons you learn from testing your plugin on its 2nd Rails application will improve its performance and stability in the Rails application you originally developed it for. You are more likely to keep all your Rails applications running on an up-to-date version of Rails too.

I have mentioned Rails and some Rails-specific terminology quite a lot, but of course the idea of setting out a standard structure in a template and branching from this could be applied to very many situations. In fact, because Rails has a pretty good directory structure by default, I’m guessing I will feel the benefit of this approach much more in non-Rails project. For example, I am doing a lot of data analysis work using just DataMapper and a console, no web framework of any kind required, and I have started to work with a certain self-imposed project structure but now I am going to set up a template so that I don’t succumb to the temptation to re-invent this structure on every project.

One of the reasons I am pretty excited about sharing code between applications easily is that I have been developing some very fun tools over the past few weeks for rapid model prototyping in Rails using rspec and railroad. Stay tuned.