![]() |
|
![]() |
![]() |
Ruby on Rails Tutorial
Learn Rails by Example
Michael Hartl
Contents
- Chapter 1 From zero to deploy
- Chapter 2 A demo app
- Chapter 3 Mostly static pages
- Chapter 4 Rails-flavored Ruby
- Chapter 5 Filling in the layout
- Chapter 6 Modeling and viewing users, part I
- Chapter 7 Modeling and viewing users, part II
- Chapter 8 Sign up
- Chapter 9 Sign in, sign out
- Chapter 10 Updating, showing, and deleting users
- Chapter 11 User microposts
- Chapter 12 Following users
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, 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 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 tests for the new action and corresponding view (Listing 9.1). (This pattern should start to look familiar by now.)
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).
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 request | URL | Named route | Action | Purpose |
|---|---|---|---|---|
| GET | /signin | signin_path | new | page for a new session (signin) |
| POST | /sessions | sessions_path | create | create a new session |
| DELETE | /signout | signout_path | destroy | delete a session (sign out) |
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).
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.

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.
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.

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).
<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.

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.
create action. app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
.
.
.
def create
render 'new'
end
.
.
.
end

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.
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@example.com", :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.
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).
There’s a subtle difference between flash and flash.now. The flash variable is designed to be used before a redirect, and it persists on the resulting page for one request—that is, it appears once, and disappears when you click on another link. Unfortunately, this means that if we don’t redirect, and instead simply render a page (as in Listing 9.8), the flash message persists for two requests: it appears on the rendered page but is still waiting for a “redirect” (i.e., a second request), and thus appears again if you click a link.
To avoid this weird behavior, when rendering rather than redirecting we use flash.now instead of flash. The flash.now object is specifically designed for displaying flash messages on rendered pages. If you ever find yourself wondering why a flash message is showing up where you don’t expect it, the chances are good that you need to replace flash with flash.now.

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.

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.
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.)
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 test 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).
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.
Because HTTP is a stateless protocol, web applications requiring user signin must implement a way to track each user’s progress from page to page. One technique for maintaining the user signin status is to use a traditional Rails session (via the special session function) to store a remember token equal to the user’s id:
session[:remember_token] = user.id
This session object makes the user id available from page to page by storing it in a cookie that expires upon browser close. On each page, the application can simply call
User.find_by_id(session[:remember_token])
to retrieve the user. Because of the way Rails handles sessions, this process is secure; if a malicious user tries to spoof the user id, Rails will detect a mismatch based on a special session id generated for each session.
For our application’s design choice, which involves persistent sessions—that is, signin status that lasts even after browser close—storing the user id is a security hole. As soon as we break the tie between the special session id and the stored user id, a malicious user could sign in as that user with a remember_token equal to the user’s id. To fix this flaw, we generate a unique, secure remember token for each user based on the user’s salt and id. Moreover, a permanent remember token would also represent a security hole—by inspecting the browser cookies, a malicious user could find the token and then use it to sign in from any other computer, any time. We solve this by adding a timestamp to the token, and reset the token every time the user signs into the application. This results in a persistent session essentially impervious to attack.
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.6 Let’s start popping them off.
sign_in function. app/helpers/sessions_helper.rb
module SessionsHelper
def sign_in(user)
cookies.permanent.signed[:remember_token] = [user.id, user.salt]
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 twenty 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.

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.)
The code above shows the importance of using new_record? in Listing 7.10 to save the salt only upon user creation. Otherwise, the salt would change each time the user was saved, preventing the retrieval of the session’s user in Section 9.3.3.
20.years.from_now
You may recall from Section 4.4.2 that Ruby lets you add methods to any class, even built-in ones. In that section, we added a palindrome? method to the String class (and discovered as a result that "deified" is a palindrome), and also saw how Rails adds a blank? method to class Object (so that "".blank?, " ".blank?, and nil.blank? are all true). The cookie code in Listing 9.12 shows yet another example of this practice, through one of Rails’ time helpers, which are methods added to Fixnum (the base class for numbers):
$ rails console >> 1.year.from_now => Sun, 13 Mar 2011 03:38:55 UTC +00:00 >> 10.weeks.ago => Sat, 02 Jan 2010 03:39:14 UTC +00:00
Rails adds other helpers, too:
>> 1.kilobyte => 1024 >> 5.megabytes => 5242880
These are useful for upload validations, making it easy to restrict, say, image uploads to 5.megabytes.
Though it must be used with caution, the flexibility to add methods to built-in classes allows for extraordinarily natural additions to plain Ruby. Indeed, much of the elegance of Rails ultimately derives from the malleability of the underlying Ruby language.
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]
current_user = user
end
end
Our focus now is the second line:
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).
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
current_user = user
is an assignment. Ruby has a special syntax for defining such an assignment function, shown in Listing 9.14.
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).
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 the cookie created by the code in Listing 9.12, as shown in Listing 9.16.
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. 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.8
||= ?
The ||= construction is very Rubyish—that is, it is highly characteristic of the Ruby language—and hence important to learn if you plan on doing much Ruby programming. Though at first it may seem mysterious, or equals is easy to understand by analogy.
We start by noting a common idiom for changing a currently defined variable. Many computer programs involve incrementing a variable, as in
x = x + 1
Most languages provide a syntactic shortcut for this operation; in Ruby (and in C, C++, Perl, Python, Java, etc.) it appears as follows:
x += 1
Analogous constructs exist for other operators as well:
$ rails console >> x = 1 => 1 >> x += 1 => 2 >> x *= 3 => 6 >> x -= 7 => -1
In each case, the pattern is that x = x O y and x O= y are equivalent for any operator O.
Another common Ruby pattern is assigning to a variable if it’s nil but otherwise leaving it alone. Recalling the or operator || seen in Section 4.2.3, we can write this as follows:
>> @user => nil >> @user = @user || "the user" => "the user" >> @user = @user || "another user" => "the user"
Since nil is false in a boolean context, the first assignment is nil || "the user", which evaluates to "the user"; similarly, the second assignment is "the user" || "the user", which also evaluates to "the user"—since strings are true in a boolean context, the series of || expressions terminates after the first expression is evaluated. (This practice of evaluating || expressions from left to right and stopping on the first true value is known as short-circuit evaluation.)
Comparing the console sessions for the various operators, we see that @user = @user || value follows the x = x O y pattern with || in the place of O, which suggests the following equivalent construction:
>> @user ||= "the user" => "the user"
Voilà!
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(*[2, 3])
=> 5
The reason this is needed in Listing 9.16 is that cookies.signed[:remember_me] 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_me] 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.9
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.
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 firsts 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).
There are 10 kinds of people in the world: Those who like the ternary operator, those who don’t, and those who don’t know about it. (If you happen to be in the third category, soon you won’t be any longer.)
When you do a lot of programming, you quickly learn that one of the most common bits of control flow goes something like this:
if boolean?
do_one_thing
else
do_something_else
end
Ruby, like many other languages (including C/C++, Perl, PHP, and Java), allows you to replace this with a much more compact expression using the ternary operator (so called because it consists of three parts):
boolean? ? do_one_thing : do_something_else
You can also use the ternary operator to replace assignment:
if boolean?
var = foo
else
var = bar
end
becomes
var = boolean? ? foo : bar
The ternary operator is common in idiomatic Ruby, so it’s a good idea to look for opportunities to use it.
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).
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 set its current_user to 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.10
test_sign_in function to simulate user signin inside tests. spec/spec_helper.rb
.
.
.
Rspec.configure do |config|
.
.
.
def test_sign_in(user)
controller.current_user = 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).11
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).
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).
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]
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 As of this writing, on my system the test in Listing 9.20 only passes if I use an explicit self in the assignment of nil to the current user:
self.current_user = nil
(In this context, self is the controller.) This shouldn’t be necessary, and the application code works fine if we write
current_user = nil
instead. This second construction is more succinct, and you might want to check to see if your test passes in this case.
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).
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).
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 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.
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.
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.
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.)
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
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).

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.
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 -am "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.
- Several of the integration specs use the same code to sign a user in. Replace that code with the
integration_sign_infunction in Listing 9.31 and verify that the tests still pass. - Use
sessioninstead ofcookiesso that users are automatically signed out when they close their browsers.16 Hint: Do a Google search on “Rails session”. - (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
newandcreateactions. Hint: Take a look at the ssl_requirement plugin. 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.)
spec/spec_helper.rb
.
.
.
Rspec.configure do |config|
.
.
.
def test_sign_in(user)
controller.current_user = 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
- 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. ↑
- We’ll see in Section 9.3.2 just how long “forever” is. ↑
- If given the
createanddestroyactions 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 fromgenerateand instead define the actions by hand. ↑ - If case you’re wondering why we use
userinstead of@userin 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@userstill works, though.) ↑ - Image from http://www.flickr.com/photos/hermanusbackpackers/3343254977/. ↑
- On some systems, you might need to use
self.current_user = userto get the upcoming tests to pass. ↑ - In fact, the two are exactly equivalent;
attr_accessoris merely a convenient way to create just such getter/setter methods automatically. ↑ - This optimization technique to avoid repeated function calls is known as memoization. ↑
- This feels like the tail wagging the dog, but that’s the price we pay for being on the cutting edge. ↑
- If you are using Spork, this will be located inside the
Spork.preforkblock. ↑ - I would prefer to test that
controller.should_not be_signed_in, but unfortunately this currently breaks due to poor testing support of signed cookies. Using the expectationcontroller.should_receive(:sign_out)is the next best thing. ↑ - You can learn about things like
cookies.deleteby 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.) ↑ - Note that we can use symbols in place of strings for the labels, e.g.,
fill_in :emailinstead offill_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. ↑ - Web browsers can’t actually issue DELETE requests; Rails fakes it with JavaScript. ↑
- Recall from Section 7.3.3 that we can link directly to a user object and allow Rails to figure out the appropriate URL. ↑
- Somewhat confusingly, we’ve used
cookiesto implement sessions, andsessionis implemented with cookies! ↑



