A (thankfully) long time ago in a galaxy far far away I developed web apps in Flash. And when I had to target different versions I had to go through a whole rigamarole of uninstalling and reinstalling plugins. Fast forward to now and I’m often working on projects in Ruby. I’ve found RVM and gemsets to be a lifesaver for managing multiple environments on different projects. But as the environment gets more complicated I’ve started to want to have each project live in splendid isolation, in their own Virtual Machine. My last post described getting a bare-bones Ruby development environment up and running. This post details taking that a little further, and getting a Rails Virtual Machine up and running.
Vagrant powered Rails Virtual Machine
In the last post, I showed how the initial machine was installed and configured with the packages we’d need for development. In essence we get as far as having the right version of ruby and installing bundler which we will use to grab all out other dependencies. Now we have everything we need to get up and running with our rails app, we will execute one more script to fully provision our box. Check out the
base_rails_app branch to get started:
git clone -b base_rails_app --single-branch https://github.com/eyefodder/spex.git
If you look inside the
VagrantFile you will see that now instead of running
post_up_message.sh we have renamed it to
prep_rails_app.sh and added a few commands. Let’s take a look:
cd /app echo 'installing app dependencies' bundle install echo 'creating databases (if needed)' rake db:create echo 'performing migrations' rake db:migrate echo 'seeding the db' rake db:seed
So, after jumping into the
/app directory (which is the guest machine path to our root development directory), we do the following:
bundle install— to install / update any gems specified in the
rake db:create— to create any databases specified in
rake db:migrate— to execute any pending migrations
rake db:seed— to populate the database with any data specified in
And that in essence is it! When you run
vagrant up, you can be sure you have the environment up and running and the app in the newest state ready to develop on. All you need to do to fire up the server is the following:
vagrant ssh cd /app rails s
Now your app will be accessible on
http://localhost:3001 and you can start developing. One thing to note is that in the
Vagrantfile, we have set the script to run as
privileged: false. Scripts run by Vagrant execute as the root user by default (useful for installing software). But for running bundler and rake tasks, this is a bad thing. So we just run the script as the non-privileged
vagrant user and we’re good to go. You’ll also notice that the script is set to
run: 'always' which means it will run every time we
vagrant up. This one needs a little more explanation
Why prep’ the Rails application every time?
When I have been working on projects with other people, I’ve found that the most common issues people have when running the app are usually caused by not installing required gems or not performing a migration that someone else has committed. What this script helps to do is mitigate that by ensuring that every time you bring the machine up, those regular updating tasks are performed. It’s worked really well for me but does come with one caveat: you have to make sure your scripts are idempotent.
When I first started using Puppet I was introduced to a new term – that of idempotence. What it basically means is that after you have applied commands once, they have no further effect. For example, if you run
rake db:create multiple times, no harm is done. Same goes for
bundle install and
rake db:migrate. However, for rake
db:seed you have to be a little more careful in how you write your seed code.
Beware multiple seeding
So the only real thing to watch out for in all this is that you write your
seeds.rb in such a way that if you run it multiple times you don’t end up with multiple database entries. Gratuitous use of
first_or_create! should help here. For example, instead of:
Thing.create(property1: 'foo', property2: 'bar')
go for something like this:
Thing.where(property1: 'foo').first_or_create!(property1: 'foo', property2: 'bar')
This makes for more cautious code anyway, which regardless of if you’re using this process is generally a good thing.
Once I had the Rails Virtual Machine up and running things were (almost) plain sailing. In an upcoming post I’ll talk about testing using Guard and RSpec; as well as how to get notifications from the Rails Virtual Machine to trigger Growl notifications on your desktop.