Getting Started


First Steps

The first step is to install the Rubicon system. You can download a Ruby Gem of the Rubicon prototype here, and install it as follows:

gem install rubicon-0.0.1.gem

Next, we need an app for which to write specs. We'll use the example of a public address book, which anyone can view and to which anyone can add new entries. Assuming you already have Rails installed, you can construct a new app as follows:

rails new address_book

This creates a new directory called address_book containing the application's code. The first step is to edit the app's Gemfile (in address_book/Gemfile) to tell Ruby to use Rubicon and RSpec:

source 'https://rubygems.org'
gem 'rails', '3.2.3'
gem 'rspec-rails', '2.6.1'
gem 'rubicon', '0.0.1'

Next, we install the required gems using Bundler by typing the following:

bundle install

Building an App

Now we're ready to create some code to model address book entries and initialize the database. The easiest way to do this is to use Rails's built-in scaffolding generator, which updates the database schema and also populates the controller of the new entity with some basic operations for constructing and deleting instances. We can generate this scaffolding and update the database to use the new schema as follows:

rails generate scaffold Entry name:string address:string
bundle exec rake db:migrate

At this point, we have a fully-functioning application. We can start up a server as follows:

rails s

We can navigate to http://localhost:3000/entries in a web browser to view, construct, edit, and delete entries from the address book.

Writing Specs

We are ready to write and check some simple specifications on the scaffolding that Rails has generated. We can edit the spec/controllers/entities_controller_spec.rb file to contain a spec that checks that all address book entries in the database will always be displayed on the entries index page as follows:

require 'spec_helper'
require 'rubicon'

describe EntriesController do
  it "should list all the entries" do
    Entry.forall do |e|
      get :index
      assigns[:entries].should include e
    end
  end
end

To check that this specification holds, we can run the RSpec task using Rake, in the same way we would for standard RSpec tests:

bundle exec rake spec

Finding a Counterexample

We're ready to start writing specifications related to the desired functionality of our application. The first new piece of functionality we might implement is a method for looking up an address book entry by the name associated with it. We can specify the desired functionality before implementing it:

it "should look up names correctly" do
    String.forall do |name|
      Entry.forall do |e|
        (e.name == name).implies do
          get :lookup, :name => name
          assigns[:entry].should == e
        end
      end
    end
  end

Running this specification will, of course, result in an error, since we haven't implemented the lookup method yet. We can do that easily by modifying the entries_controller.rb file to add the method:

def lookup
    @entry = Entry.find_by_name(params[:name])

    respond_to do |format|
      format.html { redirect_to @entry }
      format.json { render :json => @entry }
    end
  end

This method looks up the address book entry by the given name and redirects the user to the display page for that entry. Running the specification we wrote earlier no longer results in an error; instead, it returns false.

The specification fails because it's possible for the database to contain two different entries with the same name --- perhaps one person has two addresses, or has moved. In this case, only one of the two entries will be reachable, and it will be impossible for the lookup method to find the other one.

There are two possible ways to resolve this situation. One option would be to make it impossible for two entries to be associated with the same name. The other would be to allow the lookup method to display a set of entries with the given name. Neither solution is more correct than the other, but since a single person might actually have two addresses, or two people might exist with the same name, the second option seems more reasonable. This is the kind of design decision that is exposed quickly by specifying functionality before implementing it.