Rails Project Setup Best Practices
December 28, 2013
As a long time Rails consultant, every new Rails project I come on to I go through the same dance:
- Does this project have a useful
README.markdown
? Maybe with setup instructions? - No? Just Rail’s default
README.markdown
? Shucks. - Does this project have a database.yml file?
- No? Great. Does this project have a sample database.yml file I can copy and get sane defaults for this project?
- Does this file have a
.ruby-gemset
file? - Yes? Great. Does this
.ruby-gemset
have sane defaults, or is the gemset namedproject
or something non-obvious? - Is there a redis or solr config file I need to edit?
- Do I need to set up machine specific variables or settings anywhere? (For example, in
.env
,config/settings.yml
, orconfig/secrets.yml
, or even just inenvironments/development.rb
?). - No? Ok, great, does the app assume I’m running it on OS X with packages installed via Homebrew? (Because I’m usually not. And if I am running your project on bare metal, I prefer Macports.)
- Is there a Procfile?
- Yes? Great. Does that Procfile work for development, or is it a production-only Procfile?
- No Procfile? What services do I need to run to use the app? Redis? Solr? Some kind of worker queue mechanism?
- How do I run all the tests?
rake db:setup
rake spec
- Did something fail because I missed a config setting or some service isn’t running? If true, fix and GOTO 15.
- Awesome, it worked.
- Are there Cucumber or Selenium tests?
- Run those, somehow.
- Fire up Rails server
- When I visit the development version of the site, is there a special account I log in as? Or do I register a user account then browse through the code figuring out how to make myself an admin or registered user or what, then do that via
rails console
?
You could split these questions up into configuration questions and runtime questions. This blog entry will show best practices I try to install on (almost) every Rails project I touch.
Runtime
Runtime is the easiest, so I’ll tackle it first.
In my mind this is mostly solved by Foreman and a good Procfile, or set of Procfiles.
Setup with Procfiles and Foreman
A Procfile will launch all the services your app needs to run. Maybe you need Solr, Redis, the Rails server, and MongoDB up: you can set up a Procfile to launch and quit those services all together when you’re done.
Heroku uses Procfiles to make sure everything’s up for your app. Heroku’s usually my default, “pre-launch to mid-traction point” hosting choice because of its easy scaling and 2 minute setup process.
Heroku also provides tons of addons, adding features to your app. Sometimes these features are bug reporting or analytics, and sometimes the Heroku addons provide additional services. Two addons that do this are Redis 2 Go, and ElasticSearch from Bonsai.io.
If an app uses Redis, is deployed to Heroku, and uses the Redis 2 Go addon, then the app doesn’t need to have Redis in its Procfile
.
However, when I’m developing the app I need these services running locally.
Foreman takes care of this, reading a Procfile (a default Procfile or one I specify) and firing up all of the services just like Heroku does. Don’t Repeat Yourself in action.
When I’m setting up a project that’s getting deployed to Heroku I create two Procfiles: one Procfile
and one Procfile.development.sample
. (I add Procfile.development
to the .gitignore
file in Git).
The Procfile.development.sample
is important for two reasons:
- It lists all the services I’ll need to be running as a developer
- It can be used as is, or if a developer has say Mongo already running via a startup script, but the
Procfile.development.sample
tries to launch it again, they can copy the file, rename it toProcfile.development
, and remove the line about starting up Mongo.
When I’m not deploying to Heroku I’ll still create a Procfile.development.sample
for ease of starting up servers.
Running all the tests
Testing is big in the Rails world, and there’s a lot of ways to test Rails apps. RSpec with maybe Cucumber is usually what I see, but sometimes there’s a Selenium suite or Steak or something.
When I’m setting up a Rails project I write a quick Rake task to run all the test suites. For RSpec + Cucumber it looks something like this:
namespace :test do
desc "Run both RSpec test and Cucumber tests"
task "all" => ["spec", "cucumber"]
end
As a developer on the project - especially a new developer - I just want to type in one command and know I’ve tested all the app there is to test.
Configuration
When I’m setting up a project I create sample files for each configuration file that might be modified by a developer. So, files with names like:
config/database.sample.yml
ruby-gemset.sample
config/redis.sample.yml
.env.sample
config/secrets.sample.yml
But this still doesn’t solve our song and dance from the beginning of the blog entry: there’s still a lot to configure, even if I have sample files to copy and rename!
Like any good geek, I’ve replaced this frustration with a small shell script (template). Each project is different, and so each bin/install.sh
will look a little different, but here’s a recent one I made for a non-trivial project:
#!/bin/bash
# If you want to go fancier, see some prompts in
# <http://stackoverflow.com/questions/226703/how-do-i-prompt-for-input-in-a-linux-shell-script>
if [ ! -e Procfile.development ]
then
cp Procfile.development.sample Procfile.development
echo "Do you wish to edit Procfile.development?"
select yn in "Yes" "No"; do
case $yn in
Yes ) $EDITOR Procfile.development; break;;
No ) break;;
esac
done
fi
if [ ! -e config/database.yml ]
then
cp config/database.yml.example config/database.yml
echo "See the default database.yml?"
select yn in "Yes" "No"; do
case $yn in
Yes ) cat config/database.yml.example; break;;
No ) break;;
esac
done
echo "Do you wish to edit this database.yml?"
select yn in "Yes" "No"; do
case $yn in
Yes ) $EDITOR config/database.yml; break;;
No ) break;;
esac
done
fi
if [ ! -e config/redis.yml ]
then
cp config/redis.yml.example config/redis.yml
echo "Do you wish to edit redis.yml?"
select yn in "Yes" "No"; do
case $yn in
Yes ) $EDITOR config/redis.yml; break;;
No ) break;;
esac
done
fi
if [ ! -e .ruby-gemset ]
then
echo "Do you wish to create a .ruby-gemset file and edit it?"
select yn in "Yes" "No"; do
case $yn in
Yes ) cp .ruby-gemset.copy .ruby-gemset; $EDITOR .ruby-gemset; break;;
No ) break;;
esac
done
fi
if [ ! -e .env ]
then
cp .env.sample .env
echo "Do you wish to edit .env?"
select yn in "Yes" "No"; do
case $yn in
Yes ) $EDITOR .env; break;;
No ) break;;
esac
done
fi
It’s not the prettiest example of a shell script ever, but it’s easy and fast to modify and should run in all shells (I avoided fancy zsh tricks, even though zsh is my primary shell).
Run this and it will guide you through all the files you need to copy, asking you if you want to edit the config file when it’s in place. For opinionated files, like .ruby-gemset
, the script will ask what you want to do.
Each of my sample files contain sane default values, which should work for the developer, but they don’t have to.
Thoughtbot has some initial thoughts on project setup too (they call it bin/setup), but they take a slightly different approach (and automatically set up different things). You could use there shell script along with mine if you wished.
My Ultimate New-To-This-Project Developer Experience
Since we’re talking about developer automation and project setup, I’d like to share my own dream experience:
- checkout code from Git repo
- “Oh, look, a Vagrantfile”
$ vagrant up
- (15 minute coffee break while Vagrant boots up box and provisions it all for me, including Ruby setup)
- (During 15 minute coffee break, glance through the project’s
README.markdown
, see mention of runningbin/install.sh
) $ vagrant ssh
$ cd $PROJECT
$ bin/install.sh
- (Answers questions in install.sh and gets settings tweaked for this VM)
$ rake db:setup
$ foreman start -f Procfile.development
$ rake test:all
in a new tab. All tests pass.
Low barriers to entry, very automated project setup - help me get it set up right the first time. Help me be more productive faster.
You notice I called rake db:setup
which creates a new database, loads the schema, and loads db/seeds.rb
. Replace this step with “run migrations from 0” and “load in initial bootstrap data” if you wish. I’m usually in the “migrate from 0” camp, but usually find myself in a minority.
Anyway, If you compare the top list with this list you’ll see that the steps followed are very different. The first set of steps is hesitant: does this thing exist? Do I need to do X? The second set of setups is confident: The machine set this up for me and so hopefully everything is right.
In Summary
Here’s the best practices to take away from this blog entry:
- Consider creating a Vagrant setup for your project, including provisioning.
- Documentation in the
README.markdown
with basic “how to setup this project” instructions. - Sample config files with values that are opinionated, but since they’re copied into place, easily changable.
- A
bin/install.sh
script like mine, orbin/setup
script, like Thoughtbot’s. - A
Procfile
just for developers - A way to run all the tests, easily
- Load just enough sample data on a developer’s machine to allow them to get to major sections of your app without having to learn how to use it on day 1.
The easier it is for a developer to get up to speed on your project, the faster they can start getting real work done!