Ruby on Rails Tutorial

Learn Rails by Example

Michael Hartl

Contents

  1. Chapter 1 From zero to deploy
    1. 1.1 Introduction
      1. 1.1.1 Comments for various readers
      2. 1.1.2 “Scaling” Rails
      3. 1.1.3 Conventions in this book
    2. 1.2 Up and running
      1. 1.2.1 Development environments
        1. IDEs
        2. Text editors and command lines
        3. Browsers
        4. A note about tools
      2. 1.2.2 Ruby, RubyGems, Rails, and Git
        1. Rails Installer (Windows)
        2. Install Git
        3. Install Ruby
        4. Install RubyGems
        5. Install Rails
      3. 1.2.3 The first application
      4. 1.2.4 Bundler
      5. 1.2.5 rails server
      6. 1.2.6 Model-view-controller (MVC)
    3. 1.3 Version control with Git
      1. 1.3.1 Installation and setup
        1. First-time system setup
        2. First-time repository setup
      2. 1.3.2 Adding and committing
      3. 1.3.3 What good does Git do you?
      4. 1.3.4 GitHub
      5. 1.3.5 Branch, edit, commit, merge
        1. Branch
        2. Edit
        3. Commit
        4. Merge
        5. Push
    4. 1.4 Deploying
      1. 1.4.1 Heroku setup
      2. 1.4.2 Heroku deployment, step one
      3. 1.4.3 Heroku deployment, step two
      4. 1.4.4 Heroku commands
    5. 1.5 Conclusion
  2. Chapter 2 A demo app
    1. 2.1 Planning the application
      1. 2.1.1 Modeling users
      2. 2.1.2 Modeling microposts
    2. 2.2 The Users resource
      1. 2.2.1 A user tour
      2. 2.2.2 MVC in action
      3. 2.2.3 Weaknesses of this Users resource
    3. 2.3 The Microposts resource
      1. 2.3.1 A micropost microtour
      2. 2.3.2 Putting the micro in microposts
      3. 2.3.3 A user has_many microposts
      4. 2.3.4 Inheritance hierarchies
      5. 2.3.5 Deploying the demo app
    4. 2.4 Conclusion
  3. Chapter 3 Mostly static pages
    1. 3.1 Static pages
      1. 3.1.1 Truly static pages
      2. 3.1.2 Static pages with Rails
    2. 3.2 Our first tests
      1. 3.2.1 Testing tools
        1. Autotest
      2. 3.2.2 TDD: Red, Green, Refactor
        1. Spork
        2. Red
        3. Green
        4. Refactor
    3. 3.3 Slightly dynamic pages
      1. 3.3.1 Testing a title change
      2. 3.3.2 Passing title tests
      3. 3.3.3 Instance variables and Embedded Ruby
      4. 3.3.4 Eliminating duplication with layouts
    4. 3.4 Conclusion
    5. 3.5 Exercises
  4. Chapter 4 Rails-flavored Ruby
    1. 4.1 Motivation
      1. 4.1.1 A title helper
      2. 4.1.2 Cascading Style Sheets
    2. 4.2 Strings and methods
      1. 4.2.1 Comments
      2. 4.2.2 Strings
        1. Printing
        2. Single-quoted strings
      3. 4.2.3 Objects and message passing
      4. 4.2.4 Method definitions
      5. 4.2.5 Back to the title helper
    3. 4.3 Other data structures
      1. 4.3.1 Arrays and ranges
      2. 4.3.2 Blocks
      3. 4.3.3 Hashes and symbols
      4. 4.3.4 CSS revisited
    4. 4.4 Ruby classes
      1. 4.4.1 Constructors
      2. 4.4.2 Class inheritance
      3. 4.4.3 Modifying built-in classes
      4. 4.4.4 A controller class
      5. 4.4.5 A user class
    5. 4.5 Exercises
  5. Chapter 5 Filling in the layout
    1. 5.1 Adding some structure
      1. 5.1.1 Site navigation
      2. 5.1.2 Custom CSS
      3. 5.1.3 Partials
    2. 5.2 Layout links
      1. 5.2.1 Integration tests
      2. 5.2.2 Rails routes
      3. 5.2.3 Named routes
    3. 5.3 User signup: A first step
      1. 5.3.1 Users controller
      2. 5.3.2 Signup URL
    4. 5.4 Conclusion
    5. 5.5 Exercises
  6. Chapter 6 Modeling and viewing users, part I
    1. 6.1 User model
      1. 6.1.1 Database migrations
      2. 6.1.2 The model file
        1. Model annotation
        2. Accessible attributes
      3. 6.1.3 Creating user objects
      4. 6.1.4 Finding user objects
      5. 6.1.5 Updating user objects
    2. 6.2 User validations
      1. 6.2.1 Validating presence
      2. 6.2.2 Length validation
      3. 6.2.3 Format validation
      4. 6.2.4 Uniqueness validation
        1. The uniqueness caveat
    3. 6.3 Viewing users
      1. 6.3.1 Debug and Rails environments
      2. 6.3.2 User model, view, controller
      3. 6.3.3 A Users resource
        1. params in debug
    4. 6.4 Conclusion
    5. 6.5 Exercises
  7. Chapter 7 Modeling and viewing users, part II
    1. 7.1 Insecure passwords
      1. 7.1.1 Password validations
      2. 7.1.2 A password migration
      3. 7.1.3 An Active Record callback
    2. 7.2 Secure passwords
      1. 7.2.1 A secure password test
      2. 7.2.2 Some secure password theory
      3. 7.2.3 Implementing has_password?
      4. 7.2.4 An authenticate method
    3. 7.3 Better user views
      1. 7.3.1 Testing the user show page (with factories)
      2. 7.3.2 A name and a Gravatar
        1. A Gravatar helper
      3. 7.3.3 A user sidebar
    4. 7.4 Conclusion
      1. 7.4.1 Git commit
      2. 7.4.2 Heroku deploy
    5. 7.5 Exercises
  8. Chapter 8 Sign up
    1. 8.1 Signup form
      1. 8.1.1 Using form_for
      2. 8.1.2 The form HTML
    2. 8.2 Signup failure
      1. 8.2.1 Testing failure
      2. 8.2.2 A working form
      3. 8.2.3 Signup error messages
      4. 8.2.4 Filtering parameter logging
    3. 8.3 Signup success
      1. 8.3.1 Testing success
      2. 8.3.2 The finished signup form
      3. 8.3.3 The flash
      4. 8.3.4 The first signup
    4. 8.4 RSpec integration tests
      1. 8.4.1 Integration tests with style
      2. 8.4.2 Users signup failure should not make a new user
      3. 8.4.3 Users signup success should make a new user
    5. 8.5 Conclusion
    6. 8.6 Exercises
  9. Chapter 9 Sign in, sign out
    1. 9.1 Sessions
      1. 9.1.1 Sessions controller
      2. 9.1.2 Signin form
    2. 9.2 Signin failure
      1. 9.2.1 Reviewing form submission
      2. 9.2.2 Failed signin (test and code)
    3. 9.3 Signin success
      1. 9.3.1 The completed create action
      2. 9.3.2 Remember me
      3. 9.3.3 Current user
    4. 9.4 Signing out
      1. 9.4.1 Destroying sessions
      2. 9.4.2 Signin upon signup
      3. 9.4.3 Changing the layout links
      4. 9.4.4 Signin/out integration tests
    5. 9.5 Conclusion
    6. 9.6 Exercises
  10. Chapter 10 Updating, showing, and deleting users
    1. 10.1 Updating users
      1. 10.1.1 Edit form
      2. 10.1.2 Enabling edits
    2. 10.2 Protecting pages
      1. 10.2.1 Requiring signed-in users
      2. 10.2.2 Requiring the right user
      3. 10.2.3 Friendly forwarding
    3. 10.3 Showing users
      1. 10.3.1 User index
      2. 10.3.2 Sample users
      3. 10.3.3 Pagination
        1. Testing pagination
      4. 10.3.4 Partial refactoring
    4. 10.4 Destroying users
      1. 10.4.1 Administrative users
        1. Revisiting attr_accessible
      2. 10.4.2 The destroy action
    5. 10.5 Conclusion
    6. 10.6 Exercises
  11. Chapter 11 User microposts
    1. 11.1 A Micropost model
      1. 11.1.1 The basic model
        1. Accessible attribute
      2. 11.1.2 User/Micropost associations
      3. 11.1.3 Micropost refinements
        1. Default scope
        2. Dependent: destroy
      4. 11.1.4 Micropost validations
    2. 11.2 Showing microposts
      1. 11.2.1 Augmenting the user show page
      2. 11.2.2 Sample microposts
    3. 11.3 Manipulating microposts
      1. 11.3.1 Access control
      2. 11.3.2 Creating microposts
      3. 11.3.3 A proto-feed
      4. 11.3.4 Destroying microposts
      5. 11.3.5 Testing the new home page
    4. 11.4 Conclusion
    5. 11.5 Exercises
  12. Chapter 12 Following users
    1. 12.1 The Relationship model
      1. 12.1.1 A problem with the data model (and a solution)
      2. 12.1.2 User/relationship associations
      3. 12.1.3 Validations
      4. 12.1.4 Following
      5. 12.1.5 Followers
    2. 12.2 A web interface for following and followers
      1. 12.2.1 Sample following data
      2. 12.2.2 Stats and a follow form
      3. 12.2.3 Following and followers pages
      4. 12.2.4 A working follow button the standard way
      5. 12.2.5 A working follow button with Ajax
    3. 12.3 The status feed
      1. 12.3.1 Motivation and strategy
      2. 12.3.2 A first feed implementation
      3. 12.3.3 Scopes, subselects, and a lambda
      4. 12.3.4 The new status feed
    4. 12.4 Conclusion
      1. 12.4.1 Extensions to the sample application
        1. Replies
        2. Messaging
        3. Follower notifications
        4. Password reminders
        5. Signup confirmation
        6. RSS feed
        7. REST API
        8. Search
      2. 12.4.2 Guide to further resources
    5. 12.5 Exercises
  13. Chapter 13 Rails 3.1
    1. 13.1 Upgrading the sample app
      1. 13.1.1 Installing and configuring Rails 3.1
      2. 13.1.2 Getting to Red
      3. 13.1.3 Minor issues
        1. will_paginate
        2. Pagination spec
      4. 13.1.4 Major differences
        1. Asset directories
        2. Prototype to jQuery
    2. 13.2 New features in Rails 3.1
      1. 13.2.1 Asset pipeline
      2. 13.2.2 Reversible migrations
      3. 13.2.3 Sass and CoffeeScript
    3. 13.3 Exercises

Foreword

My former company (CD Baby) was one of the first to loudly switch to Ruby on Rails, and then even more loudly switch back to PHP (Google me to read about the drama). This book by Michael Hartl came so highly recommended that I had to try it, and Ruby on Rails Tutorial is what I used to switch back to Rails again.

Though I’ve worked my way through many Rails books, this is the one that finally made me “get” it. Everything is done very much “the Rails way”—a way that felt very unnatural to me before, but now after doing this book finally feels natural. This is also the only Rails book that does test-driven development the entire time, an approach highly recommended by the experts but which has never been so clearly demonstrated before. Finally, by including Git, GitHub, and Heroku in the demo examples, the author really gives you a feel for what it’s like to do a real-world project. The tutorial’s code examples are not in isolation.

The linear narrative is such a great format. Personally, I powered through Rails Tutorial in three long days, doing all the examples and challenges at the end of each chapter. Do it from start to finish, without jumping around, and you’ll get the ultimate benefit.

Enjoy!

Derek Sivers (sivers.org)
Formerly: Founder, CD Baby
Currently: Founder, Thoughts Ltd.

Acknowledgments

Ruby on Rails Tutorial owes a lot to my previous Rails book, RailsSpace, and hence to my coauthor Aurelius Prochazka. I’d like to thank Aure both for the work he did on that book and for his support of this one. I’d also like to thank Debra Williams Cauley, my editor on both RailsSpace and Rails Tutorial; as long as she keeps taking me to baseball games, I’ll keep writing books for her.

I’d like to acknowledge a long list of Rubyists who have taught and inspired me over the years: David Heinemeier Hansson, Yehuda Katz, Carl Lerche, Jeremy Kemper, Xavier Noria, Ryan Bates, Geoffrey Grosenbach, Peter Cooper, Matt Aimonetti, Gregg Pollack, Wayne E. Seguin, Amy Hoy, Dave Chelimsky, Pat Maddox, Tom Preston-Werner, Chris Wanstrath, Chad Fowler, Josh Susser, Obie Fernandez, Ian McFarland, Steven Bristol, Wolfram Arnold, Alex Chaffee, Giles Bowkett, Evan Dorn, Long Nguyen, James Lindenbaum, Adam Wiggins, Tikhon Bernstam, Ron Evans, Wyatt Greene, Miles Forrest, the good people at Pivotal Labs, the Heroku gang, the thoughtbot guys, and the GitHub crew. Finally, many, many readers—far too many to list—have contributed a huge number of bug reports and suggestions during the writing of this book, and I gratefully acknowledge their help in making it as good as it can be.

About the author

Michael Hartl is a programmer, educator, and entrepreneur. Michael was coauthor of RailsSpace, a best-selling Rails tutorial book published in 2007, and was cofounder and lead developer of Insoshi, a popular social networking platform in Ruby on Rails. Previously, he taught theoretical and computational physics at the California Institute of Technology (Caltech), where he received the Lifetime Achievement Award for Excellence in Teaching. Michael is a graduate of Harvard College, has a Ph.D. in Physics from Caltech, and is an alumnus of the Y Combinator entrepreneur program.

Copyright and license

Ruby on Rails Tutorial: Learn Rails by Example. Copyright © 2010 by Michael Hartl. All source code in Ruby on Rails Tutorial is available under the MIT License and the Beerware License.

   Copyright (c) 2010 Michael Hartl

   Permission is hereby granted, free of charge, to any person
   obtaining a copy of this software and associated documentation
   files (the "Software"), to deal in the Software without
   restriction, including without limitation the rights to use,
   copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following
   conditions:

   The above copyright notice and this permission notice shall be
   included in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
   OTHER DEALINGS IN THE SOFTWARE.
/*
 * ------------------------------------------------------------
 * "THE BEERWARE LICENSE" (Revision 42):
 * Michael Hartl wrote this code. As long as you retain this 
 * notice, you can do whatever you want with this stuff. If we
 * meet someday, and you think this stuff is worth it, you can
 * buy me a beer in return.
 * ------------------------------------------------------------
 */

Chapter 9 Sign in, sign out

Now that new users can sign up for our site (Chapter 8), it’s time to give registered users the ability to sign in and sign out. This will allow us to add customizations based on signin status and depending on the identity of the current user. For example, in this chapter we’ll update the site header with signin/signout links and a profile link; in Chapter 11, we’ll use the identity of a signed-in user to create microposts associated with that user, and in Chapter 12 we’ll allow the current user to follow other users of the application (thereby receiving a feed of their microposts).

Having users sign in will also allow us to implement a security model, restricting access to particular pages based on the identity of the signed-in user. For instance, as we’ll see in Chapter 10, only signed-in users will be able to access the page used to edit user information. The signin system will also make possible special privileges for administrative users, such as the ability (also in Chapter 10) to delete users from the database.

As in previous chapters, we’ll do our work on a topic branch and merge in the changes at the end:

$ git checkout -b sign-in-out

9.1 Sessions

A session is a semi-permanent connection between two computers, such as a client computer running a web browser and a server running Rails. There are several different models for session behavior common on the web: “forgetting” the session on browser close, using an optional “remember me” checkbox for persistent sessions, and remembering sessions until the user explicitly signs out.1 We’ll opt for the final of these options: when users sign in, we will remember their signin status “forever”,2 clearing the session only when the user explicitly signs out.

It’s convenient to model sessions as a RESTful resource: we’ll have a signin page for new sessions, signing in will create a session, and signing out will destroy it. We will therefore need a Sessions controller with new, create, and destroy actions. Unlike the case of the Users controller, which uses a database back-end (via the User model) to persist data, the Sessions controller will use a cookie, which is a small piece of text placed on the user’s browser. Much of the work involved in signin comes from building this cookie-based authentication machinery. In this section and the next, we’ll prepare for this work by constructing a Sessions controller, a signin form, and the relevant controller actions. (Much of this work parallels user signup from Chapter 8.) We’ll then complete user signin with the necessary cookie-manipulation code in Section 9.3.

9.1.1 Sessions controller

The elements of signing in and out correspond to particular REST actions of the Sessions controller: the signin form is handled by the new action (covered in this section), actually signing in is handled by sending a POST request to the create action (Section 9.2 and Section 9.3), and signing out is handled by sending a DELETE request to the destroy action (Section 9.4). (Recall the association of HTTP verbs with REST actions from Table 6.2.) Since we know that we’ll need a new action, we can create it when we generate the Sessions controller (just as with the Users controller in Listing 5.23):3

$ rails generate controller Sessions new
$ rm -rf spec/views
$ rm -rf spec/helpers

Now, as with the signup form in Section 8.1, we create a new file for the Sessions controller specs and add a couple of tests for the new action and corresponding view (Listing 9.1). (This pattern should start to look familiar by now.)

Listing 9.1. Tests for the new session action and view.
spec/controllers/sessions_controller_spec.rb
require 'spec_helper'

describe SessionsController do
  render_views

  describe "GET 'new'" do

    it "should be successful" do
      get :new
      response.should be_success
    end

    it "should have the right title" do
      get :new
      response.should have_selector("title", :content => "Sign in")
    end
  end
end

To get these tests to pass, we first need to add a route for the new action; while we’re at it, we’ll create all the actions needed throughout the chapter as well. We generally follow the example from Listing 6.26, but in this case we define only the particular actions we need, i.e., new, create, and destroy, and also add named routes for signin and signout (Listing 9.2).

Listing 9.2. Adding a resource to get the standard RESTful actions for sessions.
config/routes.rb
SampleApp::Application.routes.draw do
  resources :users
  resources :sessions, :only => [:new, :create, :destroy]

  match '/signup',  :to => 'users#new'
  match '/signin',  :to => 'sessions#new'
  match '/signout', :to => 'sessions#destroy'
  .
  .
  .
end

As you can see, the resources method can take an options hash, which in this case has key :only and value equal to an array of the actions the Sessions controller has to respond to. The resources defined in Listing 9.2 provide URLs and actions similar to those for users (Table 6.2), as shown in Table 9.1.

HTTP requestURLNamed routeActionPurpose
GET/signinsignin_pathnewpage for a new session (signin)
POST/sessionssessions_pathcreatecreate a new session
DELETE/signoutsignout_pathdestroydelete a session (sign out)
Table 9.1: RESTful routes provided by the sessions rules in Listing 9.2.

We can get the second test in Listing 9.1 to pass by adding the proper title instance variable to the new action, as shown in Listing 9.3 (which also defines the create and destroy actions for future reference).

Listing 9.3. Adding the title for the signin page.
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
    @title = "Sign in"
  end

  def create
  end

  def destroy
  end
end

With that, the tests in Listing 9.1 should be passing, and we’re ready to make the actual signin form.

9.1.2 Signin form

The signin form (or, equivalently, the new session form) is similar in appearance to the signup form, except with two fields (email and password) in place of four. A mockup appears in Figure 9.1.

signin_mockup
Figure 9.1: A mockup of the signin form. (full size)

Recall from Listing 8.2 that the signup form uses the form_for helper, taking as an argument the user instance variable @user:

<%= form_for(@user) do |f| %>
  .
  .
  .
<% end %>

The main difference between this and the new session form is that we have no Session model, and hence no analogue for the @user variable. This means that, in constructing the new session form, we have to give form_for slightly more information; in particular, whereas

form_for(@user)

allows Rails to infer that the action of the form should be to POST to the URL /users, in the case of sessions we need to indicate both the name of the resource and the appropriate URL:

form_for(:session, :url => sessions_path)

Since we’re authenticating users with email address and password, we need a field for each one inside the form; the result appears in Listing 9.4.

Listing 9.4. Code for the signin form.
app/views/sessions/new.html.erb
<h1>Sign in</h1>

<%= form_for(:session, :url => sessions_path) do |f| %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :password %><br />
    <%= f.password_field :password %>
  </div>
  <div class="actions">
    <%= f.submit "Sign in" %>
  </div>
<% end %>

<p>New user? <%= link_to "Sign up now!", signup_path %></p>

With the code in Listing 9.4, the signin form appears as in Figure 9.2.

signin_form
Figure 9.2: The signin form (/sessions/new). (full size)

Though you’ll soon get out of the habit of looking at the HTML generated by Rails (instead trusting the helpers to do their job), for now let’s take a look at it (Listing 9.5).

Listing 9.5. HTML for the signin form produced by Listing 9.4.
<form action="/sessions" method="post">
  <div class="field">
    <label for="session_email">Email</label><br />
    <input id="session_email" name="session[email]" size="30" type="text" />

  </div>
  <div class="field">
    <label for="session_password">Password</label><br />
    <input id="session_password" name="session[password]" size="30"
           type="password" />
  </div>
  <div class="actions">
    <input id="session_submit" name="commit" type="submit" value="Sign in" />
  </div>
</form>

Comparing Listing 9.5 with Listing 8.5, you might be able to guess that submitting this form will result in a params hash where params[:session][:email] and params[:session][:password] correspond to the email and password fields. Handling this submission—and, in particular, authenticating users based on the submitted email and password—is the goal of the next two sections.

9.2 Signin failure

As in the case of creating users (signup), the first step in creating sessions (signin) is to handle invalid input. We’ll start by reviewing what happens when a form gets submitted, and then arrange for helpful error messages to appear in the case of signin failure (as mocked up in Figure 9.3.) Finally, we’ll lay the foundation for successful signin (Section 9.3) by evaluating each signin submission based on the validity of its email/password combination.

signin_failure_mockup
Figure 9.3: A mockup of signin failure. (full size)

9.2.1 Reviewing form submission

Let’s start by defining a minimalist create action for the Sessions controller (Listing 9.6), which does nothing but render the new view. Submitting the /sessions/new form with blank fields then yields the result shown in Figure 9.4.

Listing 9.6. A preliminary version of the Sessions create action.
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  .
  .
  .
  def create
    render 'new'
  end
  .
  .
  .
end
initial_failed_signin_rails_3
Figure 9.4: The initial failed signin, with create as in Listing 9.6(full size)

Carefully inspecting the debug information in Figure 9.4 shows that, as hinted at the end of Section 9.1.2, the submission results in a params hash containing the email and password under the key :session:

--- !map:ActiveSupport::HashWithIndifferentAccess
commit: Sign in
session: !ActiveSupport::HashWithIndifferentAccess 
  password: ""
  email: ""
authenticity_token: BlO65PA1oS5vqrv591dt9B22HGSWW0HbBtoHKbBKYDQ=
action: create
controller: sessions

As with the case of user signup (Figure 8.6) these parameters form a nested hash like the one we saw in Listing 4.5. In particular, params contains a nested hash of the form

{ :session => { :password => "", :email => "" } }

This means that

params[:session]

is itself a hash:

{ :password => "", :email => "" }

As a result,

params[:session][:email]

is the submitted email address and

params[:session][:password]

is the submitted password.

In other words, inside the create action the params hash has all the information needed to authenticate users by email and password. Not coincidentally, we have already developed exactly the method needed: User.authenticate from Section 7.2.4 (Listing 7.12). Recalling that authenticate returns nil for an invalid authentication, our strategy for user signin can be summarized as follows:

def create
  user = User.authenticate(params[:session][:email],
                           params[:session][:password])
  if user.nil?
    # Create an error message and re-render the signin form.
  else
    # Sign the user in and redirect to the user's show page.
  end
end

9.2.2 Failed signin (test and code)

In order to handle a failed signin attempt, first we need to determine that it’s a failure. The tests follow the example from the analogous tests for user signup (Listing 8.6), as shown in Listing 9.7.

Listing 9.7. Tests for a failed signin attempt.
spec/controllers/sessions_controller_spec.rb
require 'spec_helper'

describe SessionsController do
  render_views
  .
  .
  .
  describe "POST 'create'" do

    describe "invalid signin" do

      before(:each) do
        @attr = { :email => "[email protected]", :password => "invalid" }
      end

      it "should re-render the new page" do
        post :create, :session => @attr
        response.should render_template('new')
      end

      it "should have the right title" do
        post :create, :session => @attr
        response.should have_selector("title", :content => "Sign in")
      end

      it "should have a flash.now message" do
        post :create, :session => @attr
        flash.now[:error].should =~ /invalid/i
      end
    end
  end
end

The application code needed to get these tests to pass appears in Listing 9.8. As promised in Section 9.2.1, we extract the submitted email address and password from the params hash, and then pass them to the User.authenticate method. If the user is not authenticated (i.e., if it’s nil), we set the title and re-render the signin form.4 We’ll handle the other branch of the if-else statement in Section 9.3; for now we’ll just leave a descriptive comment.

Listing 9.8. Code for a failed signin attempt.
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  .
  .
  .
  def create
    user = User.authenticate(params[:session][:email],
                             params[:session][:password])
    if user.nil?
      flash.now[:error] = "Invalid email/password combination."
      @title = "Sign in"
      render 'new'
    else
      # Sign the user in and redirect to the user's show page.
    end
  end
  .
  .
  .
end

Recall from Section 8.4.2 that we displayed signup errors using the User model error messages. Since the session isn’t an Active Record model, this strategy won’t work here, so instead we’ve put a message in the flash (or, rather, in flash.now; see Box 9.1). Thanks to the flash message display in the site layout (Listing 8.16), the flash[:error] message automatically gets displayed; thanks to the Blueprint CSS, it automatically gets nice styling (Figure 9.5).

failed_signin
Figure 9.5: A failed signin (with a flash message). (full size)

9.3 Signin success

Having handled a failed signin, we now need to actually sign a user in. A hint of where we’re going—the user profile page, with modified navigation links—is mocked up in Figure 9.6.5 Getting there will require some of the most challenging Ruby programming so far in this tutorial, so hang in there through the end and be prepared for a little heavy lifting. Happily, the first step is easy—completing the Sessions controller create action is a snap. Unfortunately, it’s also a cheat.

signin_success_mockup
Figure 9.6: A mockup of the user profile after a successful signin (with updated nav links).  (full size)

9.3.1 The completed create action

Filling in the area now occupied by the signin comment (Listing 9.8) is simple: upon successful signin, we sign the user in using the sign_in function, and then redirect to the profile page (Listing 9.9). We see now why this is a cheat: alas, sign_in doesn’t currently exist. Writing it will occupy the rest of this section.

Listing 9.9. The completed Sessions controller create action (not yet working).
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  .
  .
  .
  def create
    user = User.authenticate(params[:session][:email],
                             params[:session][:password])
    if user.nil?
      flash.now[:error] = "Invalid email/password combination."
      @title = "Sign in"
      render 'new'
    else
      sign_in user
      redirect_to user
    end
  end
  .
  .
  .
end

Even though we lack the sign_in function, we can still write the tests (Listing 9.10). (We’ll fill in the body of the first test in Section 9.3.3.)

Listing 9.10. Pending tests for user signin (to be completed in Section 9.3.3).
spec/controllers/sessions_controller_spec.rb
describe SessionsController do
  .
  .
  .
  describe "POST 'create'" do
    .
    .
    .
    describe "with valid email and password" do

      before(:each) do
        @user = Factory(:user)
        @attr = { :email => @user.email, :password => @user.password }
      end

      it "should sign the user in" do
        post :create, :session => @attr
        # Fill in with tests for a signed-in user.
      end

      it "should redirect to the user show page" do
        post :create, :session => @attr
        response.should redirect_to(user_path(@user))
      end
    end
  end
end

These tests don’t pass yet, but they’re a good start.

9.3.2 Remember me

We’re now in a position to start implementing our signin model, namely, remembering user signin status “forever” and clearing the session only when the user explicitly signs out. The signin functions themselves will end up crossing the traditional Model-View-Controller lines; in particular, several signin functions will need to be available in both controllers and views. You may recall from Section 4.2.5 that Ruby provides a module facility for packaging functions together and including them in multiple places, and that’s the plan for the authentication functions. We could make an entirely new module for authentication, but the Sessions controller already comes equipped with a module, namely, SessionsHelper. Moreover, helpers are automatically included in Rails views, so all we need to do to use the Sessions helper functions in controllers is to include the module into the Application controller (Listing 9.11).

Listing 9.11. Including the Sessions helper module into the Application controller.
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery
  include SessionsHelper
end

By default, all the helpers are available in the views but not in the controllers. We need the methods from the Sessions helper in both places, so we have to include it explicitly.

Now we’re ready for the first signin element, the sign_in function itself. Our authentication method is to place a remember token as a cookie on the user’s browser (Box 9.2), and then use the token to find the user record in the database as the user moves from page to page (implemented in Section 9.3.3). The result, Listing 9.12, pushes two things onto the stack: the cookies hash and current_user. Let’s start popping them off.

Listing 9.12. The complete (but not-yet-working) sign_in function.
app/helpers/sessions_helper.rb
module SessionsHelper

  def sign_in(user)
    cookies.permanent.signed[:remember_token] = [user.id, user.salt]
    self.current_user = user
  end
end

Listing 9.12 introduces the cookies utility supplied by Rails. We can use cookies as if it were a hash; each element in the cookie is itself a hash of two elements, a value and an optional expires date. For example, we could implement user signin by placing a cookie with value equal to the user’s id that expires 20 years from now:

cookies[:remember_token] = { :value   => user.id,
                             :expires => 20.years.from_now.utc }

(This code uses one of the convenient Rails time helpers, as discussed in Box 9.3.) We could then retrieve the user with code like

User.find_by_id(cookies[:remember_token])

Of course, cookies isn’t really a hash, since assigning to cookies actually saves a piece of text on the browser (as seen in Figure 9.7), but part of the beauty of Rails is that it lets you forget about that detail and concentrate on writing the application.

user_remember_token_cookie_rails_3
Figure 9.7: A secure remember token. (full size)

Unfortunately, using the user id in this manner is insecure for the same reason discussed in Box 9.2: a malicious user could simulate a cookie with the given id, thereby allowing access to any user in the system. The traditional solution before Rails 3 was to create a secure remember token associated with the User model to be used in place of the user id (see, e.g., the Rails 2.3 version of Rails Tutorial). This pattern became so common that Rails 3 now implements it for us using cookies.permanent.signed:

cookies.permanent.signed[:remember_token] = [user.id, user.salt]

The assignment value on the right-hand side is an array consisting of a unique identifier (i.e., the user’s id) and a secure value used to create a digital signature to prevent the kind of attacks described in Section 7.2. In particular, since we went to the trouble of creating a secure salt in Section 7.2.3, we can re-use that value here to sign the remember token. Under the hood, using permanent causes Rails to set the expiration to 20.years.from_now, and signed makes the cookie secure, so that the user’s id is never exposed in the browser. (We’ll see how to retrieve the user using the remember token in Section 9.3.3.)

9.3.3 Current user

In this section, we’ll learn how to get and set the session’s current user. Let’s look again at the sign_in function to see where we are:

module SessionsHelper

  def sign_in(user)
    cookies.permanent.signed[:remember_token] = [user.id, user.salt]
    self.current_user = user
  end
end

Our focus now is the second line:6

self.current_user = user

The purpose of this line is to create current_user, accessible in both controllers and views, which will allow constructions such as

<%= current_user.name %>

and

redirect_to current_user

The principal goal of this section is to define current_user.

To describe the behavior of the remaining signin machinery, we’ll first fill in the test for signing a user in (Listing 9.13).

Listing 9.13. Filling in the test for signing the user in.
spec/controllers/sessions_controller_spec.rb
describe SessionsController do
  .
  .
  .
  describe "POST 'create'" do
    .
    .
    .
    describe "with valid email and password" do

      before(:each) do
        @user = Factory(:user)
        @attr = { :email => @user.email, :password => @user.password }
      end

      it "should sign the user in" do
        post :create, :session => @attr
        controller.current_user.should == @user
        controller.should be_signed_in
      end

      it "should redirect to the user show page" do
        post :create, :session => @attr
        response.should redirect_to(user_path(@user))
      end
    end
  end
end

The new test uses the controller variable (which is available inside Rails tests) to check that the current_user variable is set to the signed-in user, and that the user is signed in:

it "should sign the user in" do
  post :create, :session => @attr
  controller.current_user.should == @user
  controller.should be_signed_in
end

The second line may be a little confusing at this point, but you can guess based on the RSpec convention for boolean methods that

controller.should be_signed_in

is equivalent to

controller.signed_in?.should be_true

This is a hint that we will be defining a signed_in? method that returns true if a user is signed in and false otherwise. Moreover, the signed_in? method will be attached to the controller, not to a user, which is why we write controller.signed_in? instead of current_user.signed_in?. (If no user is signed in, how could we call signed_in? on it?)

To start writing the code for current_user, note that the line

self.current_user = user

is an assignment. Ruby has a special syntax for defining such an assignment function, shown in Listing 9.14.

Listing 9.14. Defining assignment to current_user.
app/helpers/sessions_helper.rb
module SessionsHelper

  def sign_in(user)
    .
    .
    .
  end

  def current_user=(user)
    @current_user = user
  end
end

This might look confusing, but it simply defines a method current_user= expressly designed to handle assignment to current_user. Its one argument is the right-hand side of the assignment, in this case the user to be signed in. The one-line method body just sets an instance variable @current_user, effectively storing the user for later use.

In ordinary Ruby, we could define a second method, current_user, designed to return the value of @current_user (Listing 9.15).

Listing 9.15. A tempting but useless definition for current_user.
module SessionsHelper

  def sign_in(user)
    .
    .
    .
  end

  def current_user=(user)
    @current_user = user
  end

  def current_user
    @current_user     # Useless! Don't use this line.
  end
end

If we did this, we would effectively replicate the functionality of attr_accessor, first seen in Section 4.4.5 and used to make the virtual password attribute in Section 7.1.1.7 The problem is that it utterly fails to solve our problem: with the code in Listing 9.15, the user’s signin status would be forgotten: as soon as the user went to another page—poof!—the session would end and the user would be automatically signed out.

To avoid this problem, we can find the session user corresponding to the cookie created by the code in Listing 9.12, as shown in Listing 9.16.

Listing 9.16. Finding the current user by remember_token.
app/helpers/sessions_helper.rb
module SessionsHelper
  .
  .
  .
  def current_user
    @current_user ||= user_from_remember_token
  end

  private

    def user_from_remember_token
      User.authenticate_with_salt(*remember_token)
    end

    def remember_token
      cookies.signed[:remember_token] || [nil, nil]
    end
end

This code uses several more advanced features of Ruby, so let’s take a moment to examine them.

First, Listing 9.16 uses the common but initially obscure ||= (“or equals”) assignment operator (Box 9.4). Its effect is to set the @current_user instance variable to the user corresponding to the remember token, but only if @current_user is undefined.8 In other words, the construction

@current_user ||= user_from_remember_token

calls the user_from_remember_token method the first time current_user is called, but on subsequent invocations returns @current_user without calling user_from_remember_token.9

Listing 9.16 also uses the * operator, which allows us to use a two-element array as an argument to a method expecting two variables, as we can see in this console session:

$ rails console
>> def foo(bar, baz)
?>   bar + baz
?> end
=> nil
>> foo(1, 2)
=> 3
>> foo(*[1, 2])
=> 3

The reason this is needed in Listing 9.16 is that cookies.signed[:remember_token] returns an array of two elements—the user id and the salt—but (following usual Ruby conventions) we want the authenticate_with_salt method to take two arguments, so that it can be invoked with

User.authenticate_with_salt(id, salt)

(There’s no fundamental reason that authenticate_with_salt couldn’t take an array as an argument, but it wouldn’t be idiomatically correct Ruby.)

Finally, in the remember_token helper method defined by Listing 9.16, we use the || operator to return an array of nil values if cookies.signed[:remember_token] itself is nil:

cookies.signed[:remember_token] || [nil, nil]

The reason for this code is that the support for signed cookies inside Rails tests is still immature, and a nil value for the cookie causes spurious test breakage. Returning [nil, nil] instead fixes the issue.10

The final step to getting the code in Listing 9.16 working is to define an authenticate_with_salt class method. This method, which is analogous to the original authenticate method defined in Listing 7.12, is shown in Listing 9.17.

Listing 9.17. Adding an authenticate_with_salt method to the User model.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .

  def self.authenticate(email, submitted_password)
    user = find_by_email(email)
    return nil  if user.nil?
    return user if user.has_password?(submitted_password)
  end

  def self.authenticate_with_salt(id, cookie_salt)
    user = find_by_id(id)
    (user && user.salt == cookie_salt) ? user : nil
  end
  .
  .
  .
end

Here authenticate_with_salt first finds the user by unique id, and then verifies that the salt stored in the cookie is the correct one for that user.

It’s worth noting that this implementation of authenticate_with_salt is identical in function to the following code, which more closely parallels the authenticate method:

def self.authenticate_with_salt(id, cookie_salt)
  user = find_by_id(id)
  return nil  if user.nil?
  return user if user.salt == cookie_salt
end

In both cases, the method returns the user if user is not nil and the user salt matches the cookie’s salt, and returns nil otherwise. On the other hand, code like

(user && user.salt == cookie_salt) ? user : nil

is common in idiomatically correct Ruby, so I thought it was a good idea to introduce it. This code uses the strange but useful ternary operator to compress an if-else construction into one line (Box 9.5).

At this point, the signin test is almost passing; the only thing remaining is to define the required signed_in? boolean method. Happily, it’s easy with the use of the “not” operator !: a user is signed in if current_user is not nil (Listing 9.18).

Listing 9.18. The signed_in? helper method.
app/helpers/sessions_helper.rb
module SessionsHelper
  .
  .
  .
  def signed_in?
    !current_user.nil?
  end

  private
  .
  .
  .
end

Though it’s already useful for the test, we’ll put the signed_in? method to even better use in Section 9.4.3 and again in Chapter 10.

With that, all the tests should pass.

9.4 Signing out

As discussed in Section 9.1, our authentication model is to keep users signed in until they sign out explicitly. In this section, we’ll add this necessary signout capability. Once we’re done, we’ll add some integration tests to put our authentication machinery through its paces.

9.4.1 Destroying sessions

So far, the Sessions controller actions have followed the RESTful convention of using new for a signin page and create to complete the signin. We’ll continue this theme by using a destroy action to delete sessions, i.e., to sign out.

In order to test the signout action, we first need a way to sign in within a test. The easiest way to do this is to use the controller object we saw in Section 9.3.3 and use the sign_in helper to sign in the given user. In order to use the resulting test_sign_in function in all our tests, we need to put it in the spec helper file, as shown in Listing 9.19.11

Listing 9.19. A test_sign_in function to simulate user signin inside tests.
spec/spec_helper.rb
.
.
.
RSpec.configure do |config|
  .
  .
  .
  def test_sign_in(user)
    controller.sign_in(user)
  end
end

After running test_sign_in, the current_user will not be nil, so signed_in? will be true.

With this spec helper in hand, the test for signout is straightforward: sign in as a (factory) user and then hit the destroy action and verify that the user gets signed out (Listing 9.20).

Listing 9.20. A test for destroying a session (user signout).
spec/controllers/sessions_controller_spec.rb
describe SessionsController do
  .
  .
  .
  describe "DELETE 'destroy'" do

    it "should sign a user out" do
      test_sign_in(Factory(:user))
      delete :destroy
      controller.should_not be_signed_in
      response.should redirect_to(root_path)
    end
  end
end

The only novel element here is the delete method, which issues an HTTP DELETE request (in analogy with the get and post methods seen in previous tests), as required by the REST conventions (Table 9.1).

As with user signin, which relied on the sign_in function, user signout just defers the hard work to a sign_out function (Listing 9.21).

Listing 9.21. Destroying a session (user signout).
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  .
  .
  .
  def destroy
    sign_out
    redirect_to root_path
  end
end

As with the other authentication elements, we’ll put sign_out in the Sessions helper module (Listing 9.22).

Listing 9.22. The sign_out method in the Sessions helper module.
app/helpers/sessions_helper.rb
module SessionsHelper

  def sign_in(user)
    cookies.permanent.signed[:remember_token] = [user.id, user.salt]
    self.current_user = user
  end
  .
  .
  .
  def sign_out
    cookies.delete(:remember_token)
    self.current_user = nil
  end

  private
    .
    .
    .
end

As you can see, the sign_out method effectively undoes the sign_in method by deleting the remember token and by setting the current user to nil.12

9.4.2 Signin upon signup

In principle, we are now done with authentication, but as currently constructed there are no links to the signin or signout actions. Moreover, newly registered users might be confused, as they are not signed in by default.

We’ll fix the second problem first, starting with testing that a new user is automatically signed in (Listing 9.23).

Listing 9.23. Testing that newly signed-up users are also signed in.
spec/controllers/users_controller_spec.rb
require 'spec_helper'

describe UsersController do
  render_views
  .
  .
  .
  describe "POST 'create'" do
    .
    .
    .
    describe "success" do
      .
      .
      .
      it "should sign the user in" do
        post :create, :user => @attr
        controller.should be_signed_in
      end
      .
      .
      .
    end
  end
end

With the sign_in method from Section 9.3, getting this test to pass by actually signing in the user is easy: just add sign_in @user right after saving the user to the database (Listing 9.24).

Listing 9.24. Signing in the user upon signup.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(params[:user])
    if @user.save
      sign_in @user
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      @title = "Sign up"
      render 'new'
    end
  end

9.4.3 Changing the layout links

We come finally to a practical application of all our signin/out work: we’ll change the layout links based on signin status. In particular, as seen in the Figure 9.6 mockup, we’ll arrange for the links to change when users sign in or sign out, and we’ll also add a profile link to the user show page for signed-in users.

We start with two integration tests: one to check that a "Sign in" link appears for non-signed-in users, and one to check that a "Sign out" link appears for signed-in users; both cases verify that the link goes to the proper URL. We’ll put these tests in the layout links test we created in Section 5.2.1; the result appears in Listing 9.25.

Listing 9.25. Tests for the signin/signout links on the site layout.
spec/requests/layout_links_spec.rb
describe "Layout links" do
  .
  .
  .
  describe "when not signed in" do
    it "should have a signin link" do
      visit root_path
      response.should have_selector("a", :href => signin_path,
                                         :content => "Sign in")
    end
  end

  describe "when signed in" do

    before(:each) do
      @user = Factory(:user)
      visit signin_path
      fill_in :email,    :with => @user.email
      fill_in :password, :with => @user.password
      click_button
    end

    it "should have a signout link" do
      visit root_path
      response.should have_selector("a", :href => signout_path,
                                         :content => "Sign out")
    end

    it "should have a profile link" 
  end
end

Here the before(:each) block signs in by visiting the signin page and submitting a valid email/password pair.13 We do this instead of using the test_sign_in function from Listing 9.19 because test_sign_in doesn’t work inside integration tests for some reason. (See Section 9.6 for an exercise to make an integration_sign_in function for use in integration tests.)

The application code uses an if-then branching structure inside of Embedded Ruby, using the signed_in? method defined in Listing 9.18:

<% if signed_in? %>
<li><%= link_to "Sign out", signout_path, :method => :delete %></li>
<% else %>
<li><%= link_to "Sign in", signin_path %></li>
<% end %>

Notice that the signout link passes a hash argument indicating that it should submit with an HTTP DELETE request.14 With this snippet added, the full header partial appears as in Listing 9.26.

Listing 9.26. Changing the layout links for signed-in users.
app/views/layouts/_header.html.erb
<header>
  <%= link_to logo, root_path %>
  <nav class="round">
    <ul>
      <li><%= link_to "Home", root_path %></li>
      <li><%= link_to "Help", help_path %></li>
      <% if signed_in? %>
      <li><%= link_to "Sign out", signout_path, :method => :delete %></li>
      <% else %>
      <li><%= link_to "Sign in", signin_path %></li>
      <% end %>
    </ul>
  </nav>
</header>

In Listing 9.26 we’ve used the logo helper from the Chapter 5 exercises (Section 5.5); in case you didn’t work that exercise, the answer appears in Listing 9.27.

Listing 9.27. A helper for the site logo.
app/helpers/application_helper.rb
module ApplicationHelper
  .
  .
  .
  def logo
    image_tag("logo.png", :alt => "Sample App", :class => "round")
  end
end

Finally, let’s add a profile link. The test (Listing 9.28) and application code (Listing 9.29) are both straightforward. Notice that the profile link’s URL is simply current_user,15 which is our first use of that helpful method. (It won’t be our last.)

Listing 9.28. A test for a profile link.
spec/requests/layout_links_spec.rb
describe "Layout links" do
  .
  .
  .
  describe "when signed in" do
    .
    .
    .
    it "should have a profile link" do
      visit root_path
      response.should have_selector("a", :href => user_path(@user),
                                         :content => "Profile")
    end
  end
end
Listing 9.29. Adding a profile link.
app/views/layouts/_header.html.erb
<header>
  <%= link_to logo, root_path %>
  <nav class="round">
    <ul>
      <li><%= link_to "Home", root_path %></li>
      <% if signed_in? %>
      <li><%= link_to "Profile", current_user %></li>
      <% end %>
      <li><%= link_to "Help", help_path %></li>
      <% if signed_in? %>
      <li><%= link_to "Sign out", signout_path, :method => :delete %></li>
      <% else %>
      <li><%= link_to "Sign in", signin_path %></li>
      <% end %>
    </ul>
  </nav>
</header>

With the code in this section, a signed-in user now sees both signout and profile links, as expected (Figure 9.8).

profile_with_signout_link
Figure 9.8: A signed-in user with signout and profile links. (full size)

9.4.4 Signin/out integration tests

As a capstone to our hard work on authentication, we’ll finish with integration tests for signin and signout (placed in the users_spec.rb file for convenience). RSpec integration testing is expressive enough that Listing 9.30 should need little explanation; I especially like the use of click_link "Sign out", which not only simulates a browser clicking the signout link, but also raises an error if no such link exists—thereby testing the URL, the named route, the link text, and the changing of the layout links, all in one line. If that’s not an integration test, I don’t know what is.

Listing 9.30. An integration test for signing in and out.
spec/requests/users_spec.rb
require 'spec_helper'

describe "Users" do

  describe "signup" do
    .
    .
    .
  end

  describe "sign in/out" do

    describe "failure" do
      it "should not sign a user in" do
        visit signin_path
        fill_in :email,    :with => ""
        fill_in :password, :with => ""
        click_button
        response.should have_selector("div.flash.error", :content => "Invalid")
      end
    end

    describe "success" do
      it "should sign a user in and out" do
        user = Factory(:user)
        visit signin_path
        fill_in :email,    :with => user.email
        fill_in :password, :with => user.password
        click_button
        controller.should be_signed_in
        click_link "Sign out"
        controller.should_not be_signed_in
      end
    end
  end
end

9.5 Conclusion

We’ve covered a lot of ground in this chapter, transforming our promising but unformed application into a site capable of the full suite of registration and login behaviors. All that is needed to complete the authentication functionality is to restrict access to pages based on signin status and user identity. We’ll accomplish this task en route to giving users the ability to edit their information and giving administrators the ability to remove users from the system.

Before moving on, merge your changes back into the master branch:

$ git add .
$ git commit -m "Done with sign in"
$ git checkout master
$ git merge sign-in-out

9.6 Exercises

The second and third exercises are more difficult than usual. Solving them will require some outside research (e.g., Rails API reading and Google searches), and they can be skipped without loss of continuity.

  1. Several of the integration specs use the same code to sign a user in. Replace that code with the integration_sign_in function in Listing 9.31 and verify that the tests still pass.
  2. Use session instead of cookies so that users are automatically signed out when they close their browsers.16 Hint: Do a Google search on “Rails session”.
  3. (advanced) Some sites use secure HTTP (HTTPS) for their signin pages. Search online to learn how to use HTTPS in Rails, and then secure the Sessions controller new and create actions. Extra challenge: Write tests for the HTTPS functionality. (Note: I suggest doing this exercise only in development, which does not require obtaining an SSL certificate or setting up the SSL encryption machinery. Actually deploying an SSL-enabled site is much more difficult.)
Listing 9.31. A function to sign users in inside of integration tests.
spec/spec_helper.rb
.
.
.
RSpec.configure do |config|
  .
  .
  .
  def test_sign_in(user)
    controller.sign_in(user)
  end

  def integration_sign_in(user)
    visit signin_path
    fill_in :email,    :with => user.email
    fill_in :password, :with => user.password
    click_button
  end
end
  1. Another common model is to expire the session after a certain amount of time. This is especially appropriate on sites containing sensitive information, such as banking and financial trading accounts. 
  2. We’ll see in Section 9.3.2 just how long “forever” is. 
  3. If given the create and destroy actions as well, the generate script would make views for those actions, which we don’t need. Of course, we could delete the views, but I’ve elected to omit them from generate and instead define the actions by hand. 
  4. In case you’re wondering why we use user instead of @user in Listing 9.8, it’s because this user variable is never needed in any view, so there is no reason to use an instance variable here. (Using @user still works, though.) 
  5. Image from http://www.flickr.com/photos/hermanusbackpackers/3343254977/
  6. Because the Sessions helper module is included in the Application controller, the self variable here is the controller itself. 
  7. In fact, the two are exactly equivalent; attr_accessor is merely a convenient way to create just such getter/setter methods automatically. 
  8. Typically, this means assigning to variables that are initially nil, but note that false values will also be overwritten by the ||= operator. 
  9. This optimization technique to avoid repeated function calls is known as memoization
  10. This feels like the tail wagging the dog, but that’s the price we pay for being on the cutting edge. 
  11. If you are using Spork, this will be located inside the Spork.prefork block. 
  12. You can learn about things like cookies.delete by reading the cookies entry in the Rails API. (Since Rails API links tend to go stale quickly, use your Google-fu to find a current version.) 
  13. Note that we can use symbols in place of strings for the labels, e.g., fill_in :email instead of fill_in "Email". We used the latter in Listing 8.22, but by now it shouldn’t surprise you that Rails allows us to use symbols instead. 
  14. Web browsers can’t actually issue DELETE requests; Rails fakes it with JavaScript. 
  15. Recall from Section 7.3.3 that we can link directly to a user object and allow Rails to figure out the appropriate URL. 
  16. Somewhat confusingly, we’ve used cookies to implement sessions, and session is implemented with cookies! 
Michael Hartl is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to Amazon.com.