Skip to content
Daniel Kehoe edited this page Feb 17, 2012 · 26 revisions

Tutorial: Startup Prelaunch Signup with Rails 3.2

This is a detailed Rails tutorial showing how to create a Rails application for a startup prelaunch signup site.

This app extends the rails3-devise-rspec-cucumber example app. You can see a tutorial for the rails3-devise-rspec-cucumber example app. Devise gives you ready-made authentication and user management.

Similar Examples and Tutorials

See a list of additional Rails examples, tutorials, and starter apps.

Follow on Twitter Follow on Twitter

Follow the project on Twitter: @rails_apps. Tweet some praise if you like what you’ve found.

Tutorial Tutorial

This tutorial documents each step that you must follow to create this application. Every step is documented concisely, so a complete beginner can create this application without any additional knowledge. However, no explanation is offered for any of the steps, so if you are a beginner, you’re advised to look for an introduction to Rails elsewhere. See a list of recommended resources for Rails.

Before You Start

If you follow this tutorial closely, you’ll have a working application that closely matches the example app in this GitHub repository. The example app is your reference implementation. If you find problems with the app you build from this tutorial, download the example app (in Git speak, clone it) and use a file compare tool to identify differences that may be causing errors. On a Mac, good file compare tools are FileMerge, DiffMerge, Kaleidoscope, or Ian Baird’s Changes.

If you clone and install the example app and find problems or wish to suggest improvements, please create a GitHub issue.

To improve this tutorial, please edit this wiki page.

Creating the Application

Option One

Cut and paste the code.

To create the application, you can cut and paste the code from the tutorial into your own files. It’s a bit tedious and error-prone but you’ll have a good opportunity to examine the code closely.

Option Two

Use the ready-made application template to generate the code.

You can use an application template to generate a new Rails app with code that closely matches the tutorial. You’ll find an application template for this tutorial in the Rails Application Templates repository.

Use the command:

$ rails new myapp -m https://github.com/RailsApps/rails3-application-templates/raw/master/rails-prelaunch-signup-template.rb -T@

Use the -T flag to skip Test::Unit files.

This creates a new Rails app (with the name myapp) on your computer. It includes everything in the example app. You can read through the tutorial with the code already on your computer.

Option Three

Use the rails_apps_composer gem to create a reusuable application template.

This is optimal if you are creating a “starter app” based on this example app but wish to customize the code for your own preferences.

Each step in this tutorial has a corresponding application template recipe from the Rails Apps Composer recipes repository. You can create your own application template using the template recipes. To do so, download the Rails Apps Composer project, customize recipes as needed, and follow the instructions to create a reusable application template file.

Assumptions

Before beginning this tutorial, you need to install

  • The Ruby language (version 1.9.3)
  • Rails 3.2

Check that appropriate versions of Ruby and Rails are installed in your development environment:
$ ruby -v
$ rails -v

See Installing Rails 3.2 and Managing Rails Versions and Gems for detailed instructions and advice.

Create the Rails Application

You’ll start by generating the rails3-devise-rspec-cucumber example app using an application template.

Use the command:

$ rails new rails-prelaunch-signup -m https://raw.github.com/RailsApps/rails3-application-templates/master/rails3-devise-rspec-cucumber-template.rb -T

Use the -T flags to skip Test::Unit files.

This creates a new Rails app (named rails-prelaunch-signup) on your computer. For this tutorial, the name is “rails-prelaunch-signup.” You can use a different name if you wish.

The application generator template will ask you for your preferences. For this tutorial, choose the following preferences:

Would you like to use Haml instead of ERB? Yes.
Would you like to use RSpec instead of TestUnit? Yes.
Would you like to use factory_girl for test fixtures with RSpec? Yes.
Would you like to use machinist for test fixtures with RSpec? No.
Would you like to use Cucumber for your BDD? Yes.
Would you like to use Guard to automate your workflow? No.
Would you like to enable the LiveReload guard? No.
Would you like to use Devise for authentication? Yes.
Which front-end framework would you like for HTML5 and CSS3? Twitter Bootstrap
Would you like to use ‘rails-footnotes’ during development? No.
Would you like to set a robots.txt file to ban spiders? No.

After you create the application, switch to its folder to continue work directly in the application:

$ cd rails-prelaunch-signup

Please Remember: Edit the README

If you’re open sourcing the app on GitHub, please edit the README file to add a description of the app and your contact info. Changing the README is important if you’re using a clone of the example app. I’ve been mistaken (and contacted) as the author of apps that are copied from my example.

Set Up Source Control (Git)

If you’re creating an app for deployment into production, you’ll want to set up a source control repository at this point. If you are building a throw-away app for your own education, you may skip this step.

See instructions for Using Git with Rails.

Set Up Gems

About Required Gems

The application uses the following gems:

Set up Your Gemfile

The rails3-devise-rspec-cucumber application template sets up your gemfile.

See an example Rails 3.2 Gemfile.

See Installing Rails 3.2 for advice and details. It’s a good idea to create a new gemset using rvm, the Ruby Version Manager.

Install the Required Gems

Install the required gems on your computer:

$ bundle install

You can check which gems are installed on your computer with:

$ gem list --local

Keep in mind that you have installed these gems locally. When you deploy the app to another server, the same gems (and versions) must be available.

Haml

In this example, we’ll use Haml instead of the default “ERB” Rails template engine. The rails3-devise-rspec-cucumber example app sets up Haml. You can see details about adding Haml to Rails.

RSpec

The rails3-devise-rspec-cucumber example app uses RSpec for unit testing. Run rake -T to check that rake tasks for RSpec are available. You should be able to run rake spec to run all specs provided with the example app.

Cucumber

The rails3-devise-rspec-cucumber example app set up Cucumber for specifications and acceptance testing. You should be able to run rake cucumber (or more simply, cucumber) to run the Cucumber scenarios and steps provided with the example app.

Configure Email for Devise

Configure email by modifying

config/initializers/devise.rb

and setting the return email address for emails sent from the application.

You may need to set values for your mailhost in

config/environments/development.rb
config/environments/production.rb

Set Up the Database

Set up the database and add the default user by running the commands:

$ rake db:migrate
$ rake db:seed

Set up the database for testing:

$ rake db:test:clone

Test the Starter App

You can check that the example app runs properly by entering the command

$ rails server

To see your application in action, open a browser window and navigate to http://localhost:3000/. You should see the default user listed on the home page. When you click on the user’s name, you should be required to log in before seeing the user’s detail page.

Stop the server with Control-C.

Software Development Process

Arguably, for a simple application like this, you don’t need a lot of ceremony. There’s no need for a written specification. And no need for tests, right? Just code it up. However, for the purposes of this tutorial, I want to show the practices that establish a good software development process. Though this is only a simple app you’ll benefit by experiencing a process that can guide development of a more complex application.

Here’s the software development process we’ll use:

  • write user stories
  • write a short specification for our feature set using Cucumber scenarios
  • create acceptance tests using Cucumber step definitions
  • code each feature
  • run acceptance tests as each feature is completed

By practicing the process that leads from concept to code, you’ll be prepared to build a more complex application for your startup.

Write Your User Stories

User stories are a way to discuss and describe the requirements for a software application. By making a list of user stories, you’ll force yourself to describe what your application will do. The process of writing user stories will help you identify all the features that are needed for your application to be useful. Finally, breaking down the application’s functionality into discrete user stories will help you organize your work and track progress toward completion.

User stories are generally expressed in the following format:

As a <role>, I want <goal> so that <benefit>

As an example, here are three user stories we will implement for this application:

*Request Invitation*
As a visitor to the website
I want to request an invitation 
so I'll be notified when the site is launched

*See Invitation Requests*
As the owner of the site
I want to view a list of visitors who have requested invitations
so I can know if my offer is gaining traction

*Collect Email Addresses*
As the owner of the site
I want to collect email addresses for a mailing list
so I can send an announcement when I launch the site

You can see a list of user stories that have been implemented. There is also a list of user stories that have not been implemented. If you have ideas for additional features for this application, edit the file and submit a pull request. Or simply create a GitHub issue.

Feature: Request Invitation

The next step in our development process is to pick a user story and turn it into a specification that can guide implementation of a feature.

User Story

Here’s the user story we’ll specify and implement:

*Request Invitation*
As a visitor to the website
I want to request an invitation 
so I'll be notified when the site is launched

In many situations, your user story is all you need to guide you in coding the implementation. If you are both the product owner and a hands-on developer, you don’t need a written specification to implement a feature.

Before you begin coding directly from a user story, consider the benefits of writing a specification:

  • If you are part of a team you can use a specification to communicate what needs to be accomplished.
  • Creating a specification can help us discover the functionality we need to implement.
  • A specification will describe the functionality so we can discuss it with our business partners.
  • Specifications can become the basis for acceptance testing or integration testing.

For the purposes of this tutorial, acceptance tests and integration tests are synonymous. Acceptance tests demonstrate that developers have succeeded in implementing a specification. Integration tests assure you that your application runs as intended.

For these reasons, we want to create a detailed specification from this user story. We can use the specification to create an automated acceptance test so we know when the feature has been successfully implemented. Our acceptance tests can also serve as integration tests so we can continue to test the code as we build out or maintain the application.

We’ll use Cucumber to create our specification and acceptance tests. Not all developers use Cucumber. Some developers create integration tests using Capybara in combination with RSpec as described in Ryan Bates’s How I Test Railscast. Cucumber is appropriate when a team includes nonprogrammers who are involved in defining product requirements or there is a need for a specification and acceptance tests to be maintained independently of implementation (for example, when implementation is outsourced). For this tutorial, we may all be programmers, but using Cucumber to create a specification helps to describe the features, organize the tutorial, and break the work into discrete tasks.

Git Workflow

When you are using git for version control, you can commit every time you save a file, even for the tiniest typo fixes. If only you will ever see your git commits, no one will care. But if you are working on a team, either commercially or as part of an open source project, you will drive your fellow programmers crazy if they try to follow your work and see such “granular” commits. Instead, get in the habit of creating a git branch each time you begin work to implement a feature. When your new feature is complete, merge the branch and “squash” the commits so your comrades see just one commit for the entire feature.

Create a new git branch for this feature:

$ git checkout -b request-invitation

The command creates a new branch named “request-invitation” and switches to it, analogous to copying all your files to a new directory and moving to work in the new directory (though that is not really what happens with git).

Cucumber Scenario

Now we begin writing our specification.

The features directory contains our Cucumber feature files. We can organize Cucumber feature files any way we wish by placing them in subdirectories. For this application, we’ll organize features by roles in subdirectories for “visitors” and “owner”. Create a subdirectory features/visitors and then create the following file:

features/visitors/request_invitation.feature

Feature: Request Invitation
  As a visitor to the website
  I want to request an invitation 
  so I can be notified when the site is launched

  Background:
    Given I am not logged in

  Scenario: User views home page
    When I visit the home page
    Then I should see a button "Request Invitation"

  Scenario: User views invitation request form
    When I visit the home page
    And I click a button "Request Invitation"
    Then I should see a form with a field "Email"

  Scenario: User signs up with valid data
    When I request an invitation with valid user data
    Then I should see a message "Thank You"
    And my email address should be stored in the database
    And I should receive an email with "Request Received"

  Scenario: User signs up with invalid email
    When I request an invitation with an invalid email
    Then I should see an invalid email message

This Cucumber feature file contains the specification needed to implement the user story “Request Invitation.”

It’s important to note that user stories don’t necessarily translate directly into Cucumber features. In this case, our first user story is easily transformed into a set of Cucumber scenarios that describe the user story completely. This is not always the case; don’t be concerned if Features != User Stories as explained in Matt Wynne’s blog post.

Cucumber Step Definitions

Here we turn our specification into an automated acceptance test.

Cucumber scenarios can be read as plain English text. Alone, they serve as specifications. To create an acceptance test or integration test, we must write test code for each step in a scenario, called “step definitions.”

We’ll create step definitions for all the scenario steps in our “Feature: Request Invitation” file.

Create the following file:

features/step_definitions/visitor_steps.rb

def new_user
  @user ||= { :email => "[email protected]",
    :password => "please", :password_confirmation => "please"}
end

def invitation_request user
  visit '/users/sign_up'
  fill_in "Email", :with => user[:email]
  click_button "Request Invitation"
end

When /^I visit the home page$/ do
    visit '/'
end

Then /^I should see a button "([^\"]*)"$/ do |arg1|
  page.should have_button (arg1)
end

When /^I click a button "([^"]*)"$/ do |arg1|
  click_button (arg1)
end

Then /^I should see a form with a field "([^"]*)"$/ do |arg1|
  page.should have_content (arg1)
end

Then /^I should see a message "([^\"]*)"$/ do |arg1|
  page.should have_content (arg1)
end

Then /^my email address should be stored in the database$/ do
  pending # express the regexp above with the code you wish you had
end

Then /^I should receive an email with "([^"]*)"$/ do |arg1|
  ActionMailer::Base.deliveries.should_not be_empty
  @email = ActionMailer::Base.deliveries.last
  @email.body.should include(arg1)
end

When /^I request an invitation with valid user data$/ do
  invitation_request new_user
end

When /^I request an invitation with an invalid email$/ do
  user = new_user.merge(:email => "notanemail")
  invitation_request user
end

These step definitions accommodate all scenarios in our “Request Invitation” feature.

Cucumber uses all the step definitions in separate files in the features/step_definitions/ directory so we can group the step definitions in as many files as we want.

Be sure you’ve set up the database for testing before running Cucumber:

$ rake db:test:clone

Then we can run our integration test with the following command:

$ bundle exec cucumber features/visitors/request_invitation.feature --require features

The test should fail indicating that the home page has no button “Request Invitation.”

Implement “Request Invitation” Form

We begin implementing the actual application here.

The application’s home page doesn’t contain a “Request Invitation” form. We could add a sign-up form to the home page but we already have a sign-up form provided by Devise, our authentication gem. It’ll be easier to use the existing Devise mechanism.

Take a look at the file app/views/devise/registrations/new.html.haml. We’ll modify it to make it a “Request Invitation” form.

  • We’ll change the heading from “Sign up” to “Request Invitation.”
  • We’ll remove the password fields and add a hidden input field with a placeholder password. Devise won’t let us create a new user without a password so we’ll trick Devise by providing a placeholder password.
  • We’ll remove the user name field (you could keep it if you wish).
  • We’ll change the submit button text from “Sign up” to “Request Invitation.”
%h2 Request Invitation
= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f|
  = devise_error_messages!
  %input{:type=>'hidden', :name=>"user[password]", :value=>'please'}
  %p
    = f.label :email
    %br/
    = f.email_field :email
  %p= f.submit "Request Invitation"
= render :partial => "devise/shared/links"

Use Devise Registrations Page for the Home Page

Edit the config/routes.rb file to use the Devise registrations page as our home page.

Replace root :to => "home#index" with:

devise_scope :user do
  root :to => "devise/registrations#new"
end

Create a “Thank You” Page

For a simple “thank you” page, there’s no need to create a dynamic page with a controller and view. Instead, create a file for a static web page:

public/thankyou.html

<h1>Thank You</h1>

Redirect to “Thank You” Page After Successful Sign Up

The Devise wiki explains How to Redirect to a Specific Page on Successful Sign Up.

Override the Devise::RegistrationsController with a new controller. Create a file:

app/controllers/registrations_controller.rb

class RegistrationsController < Devise::RegistrationsController
  protected

  def after_inactive_sign_up_path_for(resource)
    '/thankyou.html'
  end
  
  def after_sign_up_path_for(resource)
    '/thankyou.html'
  end
  
end

Modify config/routes.rb to use the new controller. Replace devise_for :users with:

devise_for :users, :controllers => { :registrations => "registrations" }

Test the Implemention of the “Request Invitation” Feature

Run the integration test with the following command:

$ bundle exec cucumber features/visitors/request_invitation.feature --require features

The test should succeed. Evaluating only the functionality, the feature is complete.

Git Workflow

Since the new feature is complete, merge the working branch to “master” and squash the commits so you have just one commit for the entire feature:

$ git checkout master
$ git merge --squash request-invitation
$ git commit -am "implement 'Request Invitation' feature"

You can delete the working branch when you’re done:

$ git branch -d request-invitation
Clone this wiki locally