Since Ruby on Rails 4.2 has just been released, perhaps now is a good time to review creating a shiny new Rails project. It’s not often I get to create a new project from scratch, but it’s Christmas and I’ve got a bit of downtime – and an itch I’d like to scratch! So, let’s get started.

I’m aiming to build a wee project that keeps track of OmniFocus perspectives. I’ve noticed that people are sharing their perspectives as screenshots and descriptions, in tweets and blog posts. Wouldn’t it be awesome if there was a one-stop-shop for everybody’s perspectives?

A couple of early decisions in terms of the basic starting point:

  • Chances are I’ll deploy the app onto Heroku, so it’s a no-brainer to start out by using PostgreSQL in development. (Even if I wasn’t using Heroku, personally I’d choose it over MySQL if I could!)

  • I have no design skills whatsoever, so I’ll be using Twitter Bootstrap to provide some basic styling.

  • I’ll be using RSpec for testing – both unit tests and integration tests. I’ll give Cucumber a miss, at least for now.

  • Eventually, I’d like to move away from the asset pipeline, managing client side assets separately, but I’ll save that for a future refactoring.

With that in mind, let’s get started. I’m assuming that you’ve got suitable versions of Ruby, and Git, and that you’ve got PostgreSQL running locally. If you haven’t, but you’re on a Mac, may I politely point you in the direction of homebrew, rbenv, and ruby-build? If you’re not on Mac OS X, please feel free to help out in the comments. :)

So, let’s install Rails through Rubygems:

$ gem install rails

Now we’ll create the new project, and add the generated skeleton as our initial git commit:

$ rails new perspectives -d postgresql --skip-keeps --skip-test-unit
[ ... ]
$ cd perspectives
$ git init
$ git add .
$ git commit -m 'Skeleton Rails project.'

Now let’s set up the database configuration. Edit config/database.yml to remove the production stanza (which is provided by our production configuration management system!), and remove the boilerplate comments:

default: &default
  adapter: postgresql
  encoding: unicode
  pool: 5

development:
  <<: *default
  database: perspectives_development

test:
  <<: *default
  database: perspectives_test

Let’s create the two databases, generate the initial schema, and commit the changes back to git:

$ rake db:create:all
$ rake db:migrate
$ git add .
$ git commit -m "Database configuration."

Now we’ll just tidy up the bundler dependencies in Gemfile, just because there’s stuff in there we’ll never use. We can always add them back in at the point we need them after all. Gemfile now contains just:

source 'https://rubygems.org'

gem 'rails', '~> 4.2.0'
gem 'pg'

# Asset pipeline
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.1.0'
gem 'jquery-rails'

group :development, :test do
  gem 'byebug'
  gem 'web-console', '~> 2.0'
  gem 'spring'
end

Since we’ve removed the turbolinks gem, we’ll need to remove the reference to it from app/assets/javascripts/application.js, lest we start getting errors on every page load. Run bundle install again to tidy up the lock file, add the changes to the index and commit:

$ git add .
$ git commit -m "Tidy up gem dependencies."

It’s a good idea to set the Ruby version. This tells rbenv or rvm which version of Ruby you want to use in development, and tells Heroku which one you want in production. I’m currently working with Ruby 2.1.5, so let’s set that:

$ echo '2.1.5' > .ruby-version

We’ll get bundler to pick up that same setting, just so we’re not repeating ourselves. In Gemfile, just under the source declaration, add:

ruby File.read(File.expand_path('../.ruby-version', __FILE__)).chomp

Again, keeping with small, discrete changes, we’ll commit that:

$ git add .
$ git commit -m "Set Ruby version to 2.1.5."

We should figure out a way to run the application server. I like to use foreman to have all the app’s components up and running in development. I have it installed through the Heroku Toolbelt, but you can just install it through Rubygems, with gem install foreman. In terms of the application server itself, unless I have a compelling reason to use something different, I’ll use Unicorn. Create a Procfile in the root of the project with:

web: bundle exec unicorn --port=${PORT} --config-file config/unicorn.rb

add the ‘unicorn’ gem to our bundler dependencies, and create a Unicorn configuration file in config/unicorn.rb:

worker_processes Integer(ENV['WEB_CONCURRENCY'] || 3)
timeout 15
preload_app true

before_fork do |_server, _worker|
  Signal.trap 'TERM' do
    puts 'Unicorn master intercepting TERM signal and sending itself QUIT instead.'
    Process.kill 'QUIT', Process.pid
  end

  ActiveRecord::Base.connection.disconnect!
end

after_fork do |_server, _worker|
  Signal.trap 'TERM' do
    puts 'TERM ignored. Waiting on master sending a QUIT signal instead.'
  end

  ActiveRecord::Base.establish_connection
end

This is pretty much cargo-culted from the Heroku documentation for using Unicorn, but it’s doing a couple of key things:

  • Translating between the TERM and QUIT signals, so that Unicorn does the right thing when Heroku’s process supervision system tells it to die.

  • Makes sure that every worker process has its own connection to the PostgreSQL database, instead of sharing one inherited from the master (which doesn’t need it at all).

Now we can run:

$ foreman start

to start up an application server, and it should allow us to visit http://localhost:5000/. Commit those changes:

$ git add .
$ git commit -m "Unicorn and foreman for the app server."

Let’s setup some testing next. My testing framework of choice is RSpec. Let’s add it, and spring support, to the Gemfile:

group :development, :test do
  gem 'spring-commands-rspec'
  gem 'rspec-rails'
  gem 'capybara'
end

and run bundle install to add them to our bundle. Now generate the rspec binstub so it uses spring to load faster:

$ spring binstub --all

and generate the skeleton configuration for rspec:

$ rails g rspec:install
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

Note that two spec helpers are now generated – one that just loads and configures rspec, and one which also loads the full Rails environment. This means that, if you have code which doesn’t depend on Rails itself, you can skip loading the whole shebang, which means your tests run faster. Awesome. (Though with the help of Spring, I can’t say I’ve suffered enough from slow specs to need the separation lately.)

the spec/spec_helper.rb file comes with a sensible default configuration that’s commented out in an =begin/=end clause. They’re all useful defaults, so just remove the =begin and =end, and we’re good to start testing. Sometimes, spring gets in the way a bit, in that it needs to be manually restarted to pick up changes. This is one of those times, so let’s restart (well, stop – it’ll automatically start again when it needs to) it:

$ spring stop

Then run the specs:

$ rspec
Run options: include {:focus=>true}

All examples were filtered out; ignoring {:focus=>true}
No examples found.


Finished in 0.00029 seconds (files took 0.13203 seconds to load)
0 examples, 0 failures

Top 0 slowest examples (0 seconds, 0.0% of total time):

Randomized with seed 35401

That’s working nicely, so let’s commit our changes:

$ git add .
$ git commit -m "Add rspec for testing."

It’s handy to have quick feedback from tests. In particular, I like to have tests run automatically when I make changes to a related file. Guard has my back here, so let’s use it. Add the following to the Gemfile:

group :development, :test do
  gem 'guard-rspec'
  gem 'guard-bundler'
end

and run bundle install to install them. Now we can generate a skeleton Guardfile to configure Guard:

$ bundle exec guard init bundler rspec

The configuration in here has changed substantially since last time I generated a Guardfile! Suffice to say, all that really needs to be changed is to tell guard to use spring’s binstub to launch rspec. Change the rspec declaration to:

guard :rspec, cmd: "bin/rspec" do
  # [ ... ]
end

We can now start guard in a new shell, with:

$ bundle exec guard start

and it’ll keep an eye out for file changes, running the appropriate tests for us when a file is saved. Hit enter to run all the tests now, just to check it’s working. Tests passing? Good, let’s commit our changes:

$ git add .
$ git commit -m "Guard, for automatically running our tests."

Winning. Let’s actually start serving some content – in particular, it’s always helpful to start with a static home page. Let’s start by specifying the routes. Create spec/routing/pages_controller_routing_spec.rb with:

require 'rails_helper'

RSpec.describe PagesController do
  it 'routes the home page to pages#index' do
    expect(get: '/').to route_to('pages#index')
  end

  it 'generates / for root_path' do
    expect(root_path).to eq('/')
  end
end

then replace the contents of config/routes.rb with the following to make the specs pass:

Rails.application.routes.draw do
  root to: 'pages#index'
end

Now we’ll specify the controller itself, in spec/controllers/pages_controller_spec.rb:

require 'rails_helper'

RSpec.describe PagesController do
  describe 'GET index' do
    def do_get
      get :index
    end

    it 'returns http success' do
      do_get
      expect(response).to have_http_status(:success)
    end

    it 'renders the index template' do
      do_get
      expect(response).to render_template('index')
    end
  end
end

with the corresponding controller implementation in app/controllers/pages_controller.rb containing:

class PagesController < ApplicationController
  def index
  end
end

And, finally, a view, in app/views/pages/index.html.erb:

<h1>Hello World</h1>

Not only should our specs be green, but visiting http://localhost:5000/ should reveal the newly minted view being rendered. Just for completeness, we’ll add a feature spec as a simple integration test to check the entire stack is working. Create spec/features/pages_spec.rb with:

require 'rails_helper'

RSpec.feature 'Static pages' do
  scenario 'Visiting the home page' do
    visit '/'

    expect(page).to have_text('Hello World')
  end
end

Let’s commit our changes and move on to the final stage:

$ git add .
$ git commit -m "Add in a static home page."

The final stage is to make things look a little bit prettier, and for that we’ll use Bootstrap. In particular, since we’re still using the asset pipeline, in order to reduce the number of dependencies, we’ll use the SASS port of Bootstrap. Add the following to the Gemfile:

gem 'bootstrap-sass'
gem 'autoprefixer-rails'

and run bundle install to install it. Rename the application stylesheet so it’s processed as sass:

git mv app/assets/stylesheets/application.css{,.scss}

and add the following to the end:

@import "bootstrap-sprockets";
@import "bootstrap";

Import the Bootstrap Javascript, in app/assets/javascripts/application.js:

//= require bootstrap-sprockets

Finally, create a layout suitable for bootstrap, in app/views/layouts/application.html.erb:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <%= tag :link, { rel: 'shortcut icon', href: image_path('favicon.ico') }, true %>
    <title>
      <%= content_for?(:title) ? "#{yield :title} - " : "" %>OmniFocus Perspectives
    </title>

    <%= stylesheet_link_tag 'application', media: 'all' %>
    <%= csrf_meta_tags %>
  </head>
  <body>
    <nav class="navbar navbar-default" role="navigation">
      <div class="container-fluid">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#primary-navbar-collapse">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <%= link_to 'OmniFocus Perspectives', root_path, class: 'navbar-brand' %>
        </div>

        <div class="collapse navbar-collapse" id="primary-navbar-collapse">
          <ul class="nav navbar-nav">
            <% if request.path == root_path %>
              <li class="active"><%= link_to 'Home', root_path %></li>
            <% else %>
              <li><%= link_to 'Home', root_path %></li>
            <% end %>
          </ul>
        </div>
      </div>
    </nav>

    <div class="container">

      <% if content_for?(:title) %>
        <hgroup class="page-header">
          <% if content_for?(:toolbar) %>
            <div class="pull-right">
              <%= yield :toolbar %>
            </div>
          <% end %>

          <h1><%= yield :title %></h1>
        </hgroup>
      <% end %>

      <section id="flash">
        <% if notice.present? %>
          <div class="alert alert-success alert-dismissible" role="alert">
            <button type="button" class="close" data-dismiss="alert">
              <span aria-hidden="true">&times;</span>
              <span class="sr-only">Close</span>
            </button>

            <%= notice.html_safe %>
          </div>
        <% end %>

        <% if alert.present? %>
          <div class="alert alert-danger alert-dismissible" role="alert">
            <button type="button" class="close" data-dismiss="alert">
              <span aria-hidden="true">&times;</span>
              <span class="sr-only">Close</span>
            </button>

            <%= alert.html_safe %>
          </div>
        <% end %>
      </section>

    <article id="main" role="main">
      <%= yield %>
    </article>

    <%= javascript_include_tag 'application' %>
  </body>
</html>

It’s a bit of a mouthful, but that’s the template I wind up starting with for all my new projects. It gives me a nice title, a menu bar, flash messages, and a prettily styled body. A good start for putting together the rest of the app. Finally, commit that change.

That’s about it for getting started on a new Rails project. All in all – especially when I crib from another project – it takes about half an hour to get up and running with a full test suite, a pretty UI, and a pleasant development environment. Now it’s time to implement the first feature!

A Sneak Peek at
The Internet

If you enjoyed this article, you might be interested in my new project, A Sneak Peek at The Internet. What happens when you enter www.facebook.com into your web browser and hit return? A Sneak Peek at The Internet will take you on a deep dive through the network stack, from HTTP, SSL, TCP and IP, all the way down through the data link layer, back up to Facebook's data centres, and then on the return journey back to the browser.

There's more fun, excitement and peril than a Disneyland rollercoaster!