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 10 Updating, showing, and deleting users

In this chapter, we will complete the REST actions for the Users resource (Table 6.2) by adding edit, update, index, and destroy actions. We’ll start by giving users the ability to update their profiles, which will also provide a natural opportunity to enforce a security model (made possible by the authentication code in Chapter 9). Then we’ll make a listing of all users (also requiring authentication), which will motivate the introduction of sample data and pagination. Finally, we’ll add the ability to destroy users, wiping them clear from the database. Since we can’t allow just any user to have such dangerous powers, we’ll take care to create a privileged class of administrative users (admins) along the way.

To get started, let’s start work on an updating-users topic branch:

$ git checkout -b updating-users

10.1 Updating users

The pattern for editing user information closely parallels that for creating new users (Chapter 8). Instead of a new action rendering a view for new users, we have an edit action rendering a view to edit users; instead of create responding to a POST request, we have an update action responding to a PUT request (Box 3.1). The biggest difference is that, while anyone can sign up, only the current user should be able to update their information. This means that we need to enforce access control so that only authorized users can edit and update; the authentication machinery from Chapter 9 will allow us to use a before filter to ensure that this is the case.

10.1.1 Edit form

We start with tests for the edit form, whose mockup appears in Figure 10.1.1 Two are analogous to tests we saw for the new user page (Listing 8.1), checking for the proper response and title; the third test makes sure that there is a link to edit the user’s Gravatar image (Section 7.3.2). If you poke around the Gravatar site, you’ll see that the page to add or edit images is (somewhat oddly) located at http://gravatar.com/emails, so we test the edit page for a link with that URI.2 The result is shown in Listing 10.1.

edit_user_mockup
Figure 10.1: A mockup of the user edit page. (full size)
Listing 10.1. Tests for the user edit action.
spec/controllers/users_controller_spec.rb
require 'spec_helper'

describe UsersController do
  render_views
  .
  .
  .
  describe "GET 'edit'" do

    before(:each) do
      @user = Factory(:user)
      test_sign_in(@user)
    end

    it "should be successful" do
      get :edit, :id => @user
      response.should be_success
    end

    it "should have the right title" do
      get :edit, :id => @user
      response.should have_selector("title", :content => "Edit user")
    end

    it "should have a link to change the Gravatar" do
      get :edit, :id => @user
      gravatar_url = "http://gravatar.com/emails"
      response.should have_selector("a", :href => gravatar_url,
                                         :content => "change")
    end
  end
end

Here we’ve made sure to use test_sign_in(@user) to sign in as the user in anticipation of protecting the edit page from unauthorized access (Section 10.2). Otherwise, these tests would break as soon as we implemented our authentication code.

Note from Table 6.2 that the proper URI for a user’s edit page is /users/1/edit (assuming the user’s id is 1). Recall that the id of the user is available in the params[:id] variable, which means that we can find the user with the code in Listing 10.2. This uses find to find the relevant user in the database, and then sets the @title variable to the proper value.

Listing 10.2. The user edit action.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def edit
    @user = User.find(params[:id])
    @title = "Edit user"
  end
end

Getting the tests to pass requires making the actual edit view, shown in Listing 10.3. Note how closely this resembles the new user view from Listing 8.2; the large overlap suggests factoring the repeated code into a partial, which is left as an exercise (Section 10.6).

Listing 10.3. The user edit view.
app/views/users/edit.html.erb
<h1>Edit user</h1>

<%= form_for(@user) do |f| %>
  <%= render 'shared/error_messages', :object => f.object %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <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="field">
    <%= f.label :password_confirmation, "Confirmation" %><br />
    <%= f.password_field :password_confirmation %>
  </div>
  <div class="actions">
    <%= f.submit "Update" %>
  </div>
<% end %>

<div>
  <%= gravatar_for @user %>
  <a href="http://gravatar.com/emails">change</a>
</div>

Here we have reused the shared error_messages partial introduced in Section 8.2.3.

You may recall from Listing 8.8 that the error-messages partial references the @user variable explicitly. In the present case, we do happen to have an @user variable, but in order to make this a truly shared partial we should not depend on this fact. The solution is to pass the object corresponding to the form variable f as a parameter to the partial:

<%= render 'shared/error_messages', :object => f.object %>

This creates a variable called object in the partial, which we can then use to generate the error messages, as shown in Listing 10.4. (Note the fancy chain of methods to get a nice version of the object name; see the Rails API entry on, say, humanize, to get an idea of the range of Rails utilities available.)

Listing 10.4. Updating the error-messages partial from Listing 8.9 to work with other objects.
app/views/shared/_error_messages.html.erb
<% if object.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(object.errors.count, "error") %> 
        prohibited this <%= object.class.to_s.underscore.humanize.downcase %> 
        from being saved:</h2>
    <p>There were problems with the following fields:</p>
    <ul>
    <% object.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

This uses the any? method we saw in Section 8.2.3. While we’re at it, we’ll update the signup form with the more general code (Listing 10.5).

Listing 10.5. Updating the rendering of user signup errors.
app/views/users/new.html.erb
<h1>Sign up</h1>

<%= form_for(@user) do |f| %>
  <%= render 'shared/error_messages', :object => f.object %>
  .
  .
  .
<% end %>

We’ll also add a link to the site navigation for the user edit page (which we’ll call “Settings”), as mocked up in Figure 10.23 and shown in Listing 10.6.

profile_settings_link_mockup
Figure 10.2: A mockup of the user profile page with a “Settings” link. (full size)
Listing 10.6. Adding a Settings 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>
      <li><%= link_to "Settings", edit_user_path(current_user) %></li>
      <% end %>
      .
      .
      .
    </ul>
  </nav>
</header>

Here we use the named route edit_user_path from Table 6.2, together with the handy current_user helper method defined in Listing 9.16.

With the @user instance variable from Listing 10.2, the tests from Listing 10.1 pass. As seen in Figure 10.3, the edit page renders, though it doesn’t yet work.

edit_user_settings
Figure 10.3: Editing user settings (/users/1/edit). (full size)

Looking at the HTML source for Figure 10.3, we see a form tag as expected (Listing 10.7).

Listing 10.7. HTML for the edit form defined in Listing 10.3 and shown in Figure 10.3.
<form action="/users/1" class="edit_user" id="edit_user_1" method="post">
  <input name="_method" type="hidden" value="put" />
  .
  .
  .
</form>

Note here the hidden input field

<input name="_method" type="hidden" value="put" />

Since web browsers can’t natively send PUT requests (as required by the REST conventions from Table 6.2), Rails fakes it with a POST request and a hidden input field.4

There’s another subtlety to address here: the code form_for(@user) in Listing 10.3 is exactly the same as the code in Listing 8.2—so how does Rails know to use a POST request for new users and a PUT for editing users? The answer is that it is possible to tell whether a user is new or already exists in the database via Active Record’s new_record? boolean method:

$ rails console
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false

When constructing a form using form_for(@user), Rails uses POST if @user.new_record? is true and PUT if it is false.

10.1.2 Enabling edits

Although the edit form doesn’t yet work, we’ve outsourced image upload to Gravatar, so it works straightaway by clicking on the “change” link from Figure 10.3, as shown in Figure 10.4. Let’s get the rest of the user edit functionality working as well.

gravatar_cropper
Figure 10.4: The Gravatar image-cropping interface, with a picture of some dude(full size)

The tests for the update action are similar to those for create. In particular, we test both update failure and update success (Listing 10.8). (This is a lot of code; see if you can work through it by referring back to the tests in Chapter 8.)

Listing 10.8. Tests for the user update action.
spec/controllers/users_controller_spec.rb
describe UsersController do
  render_views
  .
  .
  .
  describe "PUT 'update'" do

    before(:each) do
      @user = Factory(:user)
      test_sign_in(@user)
    end

    describe "failure" do

      before(:each) do
        @attr = { :email => "", :name => "", :password => "",
                  :password_confirmation => "" }
      end

      it "should render the 'edit' page" do
        put :update, :id => @user, :user => @attr
        response.should render_template('edit')
      end

      it "should have the right title" do
        put :update, :id => @user, :user => @attr
        response.should have_selector("title", :content => "Edit user")
      end
    end

    describe "success" do

      before(:each) do
        @attr = { :name => "New Name", :email => "user@example.org",
                  :password => "barbaz", :password_confirmation => "barbaz" }
      end

      it "should change the user's attributes" do
        put :update, :id => @user, :user => @attr
        @user.reload
        @user.name.should  == @attr[:name]
        @user.email.should == @attr[:email]
      end

      it "should redirect to the user show page" do
        put :update, :id => @user, :user => @attr
        response.should redirect_to(user_path(@user))
      end

      it "should have a flash message" do
        put :update, :id => @user, :user => @attr
        flash[:success].should =~ /updated/
      end
    end
  end
end

The only novelty here is the reload method, which appears in the test for changing the user’s attributes:

it "should change the user's attributes" do
  @user.reload
  @user.name.should  == @attr[:name]
  @user.email.should == @attr[:email]
end

This code reloads the @user variable from the (test) database using @user.reload, and then verifies that the user’s new name and email match the attributes in the @attr hash.

The update action needed to get the tests in Listing 10.8 to pass is similar to the final form of the create action (Listing 9.24), as seen in Listing 10.9.

Listing 10.9. The user update action.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def update
    @user = User.find(params[:id])
    if @user.update_attributes(params[:user])
      flash[:success] = "Profile updated."
      redirect_to @user
    else
      @title = "Edit user"
      render 'edit'
    end
  end
end

With that, the user edit page should be working. As currently constructed, every edit requires the user to reconfirm the password (as implied by the empty confirmation text box in Figure 10.3), which makes updates more secure but is a minor annoyance.

10.2 Protecting pages

Although the edit and update actions from Section 10.1 are functionally complete, they suffer from a ridiculous security flaw: they allow anyone (even non-signed-in users) to access either action, and any signed-in user can update the information for any other user. In this section, we’ll implement a security model that requires users to be signed in and prevents them from updating any information other than their own. Users who aren’t signed in and who try to access protected pages will be forwarded to the signin page with a helpful message, as mocked up in Figure 10.5.

signin_page_protected_mockup
Figure 10.5: A mockup of the result of visiting a protected page (full size)

10.2.1 Requiring signed-in users

Since the security restrictions for the edit and update actions are identical, we’ll handle them in a single RSpec describe block. Starting with the sign-in requirement, our initial tests verify that non-signed-in users attempting to access either action are simply redirected to the signin page, as seen in Listing 10.10.

Listing 10.10. The first tests for authentication.
spec/controllers/users_controller_spec.rb
describe UsersController do
  render_views
  .
  .
  .
  describe "authentication of edit/update pages" do

    before(:each) do
      @user = Factory(:user)
    end

    describe "for non-signed-in users" do

      it "should deny access to 'edit'" do
        get :edit, :id => @user
        response.should redirect_to(signin_path)
      end

      it "should deny access to 'update'" do
        put :update, :id => @user, :user => {}
        response.should redirect_to(signin_path)
      end
    end
  end
end

The application code gets these tests to pass using a before filter, which arranges for a particular method to be called before the given actions. In this case, we define an authenticate method and invoke it using before_filter :authenticate, as shown in Listing 10.11.

Listing 10.11. Adding an authenticate before filter.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_filter :authenticate, :only => [:edit, :update]
  .
  .
  .
  private

    def authenticate
      deny_access unless signed_in?
    end
end

By default, before filters apply to every action in a controller, so here we restrict the filter to act only on the :edit and :update actions by passing the :only options hash.

This code won’t work yet, because deny_access hasn’t been defined. Since access denial is part of authentication, we’ll put it in the Sessions helper from Chapter 9. All deny_access does is put a message in flash[:notice] and then redirect to the signin page (Listing 10.12).

Listing 10.12. The deny_access method for user authentication.
app/helpers/sessions_helper.rb
module SessionsHelper
  .
  .
  .
  def deny_access
    redirect_to signin_path, :notice => "Please sign in to access this page."
  end
  .
  .
  .
end

Note here that Listing 10.12 uses a shortcut for setting flash[:notice] by passing an options hash to the redirect_to function. The code in Listing 10.12 is equivalent to the more verbose

flash[:notice] = "Please sign in to access this page."
redirect_to signin_path

(The same construction works for the :error key, but not for :success.)

Together with :success and :error, the :notice key completes our triumvirate of flash styles, all of which are supported natively by Blueprint CSS. By signing out and attempting to access the user edit page /users/1/edit, we can see the resulting yellow "notice" box, as seen in Figure 10.6.

protected_sign_in
Figure 10.6: The signin form after trying to access a protected page. (full size)

10.2.2 Requiring the right user

Of course, requiring users to sign in isn’t quite enough; users should only be allowed to edit their own information. We can test for this by first signing in as an incorrect user and then hitting the edit and update actions (Listing 10.13). Note that, since users should never even try to edit another user’s profile, we redirect not to the signin page but to the root url.

Listing 10.13. Authentication tests for signed-in users.
spec/controllers/users_controller_spec.rb
describe UsersController do
  render_views
  .
  .
  .
  describe "authentication of edit/update pages" do
    .
    .
    .
    describe "for signed-in users" do

      before(:each) do
        wrong_user = Factory(:user, :email => "user@example.net")
        test_sign_in(wrong_user)
      end

      it "should require matching users for 'edit'" do
        get :edit, :id => @user
        response.should redirect_to(root_path)
      end

      it "should require matching users for 'update'" do
        put :update, :id => @user, :user => {}
        response.should redirect_to(root_path)
      end
    end
  end
end

The application code is simple: we add a second before filter to call the correct_user method (which we have to write), as shown in Listing 10.14.

Listing 10.14. A correct_user before filter to protect the edit/update pages.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_filter :authenticate, :only => [:edit, :update]
  before_filter :correct_user, :only => [:edit, :update]
  .
  .
  .
  def edit
    @title = "Edit user"
  end
  .
  .
  .
  private

    def authenticate
      deny_access unless signed_in?
    end

    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_path) unless current_user?(@user)
    end
end

This uses the current_user? method, which (as with deny_access) we define in the Sessions helper (Listing 10.15).

Listing 10.15. The current_user? method.
app/helpers/sessions_helper.rb
module SessionsHelper
  .
  .
  .
  def current_user?(user)
    user == current_user
  end

  def deny_access
    redirect_to signin_path, :notice => "Please sign in to access this page."
  end

  private
    .
    .
    .
end

Listing 10.14 also shows the updated edit action. Before, in Listing 10.2, we had

def edit
  @user = User.find(params[:id])
  @title = "Edit user"
end

but now that the correct_user before filter defines @user we can omit it from the edit action (and from the update action as well).

10.2.3 Friendly forwarding

Our page protection is complete as written, but there is one minor blemish: when users try to access a protected page, they are currently redirected to their profile pages regardless of where they were trying to go. In other words, if a non-logged-in user tries to visit the edit page, after signing in the user will be redirected to /users/1 instead of /users/1/edit. It would be much friendlier to redirect them to their intended destination instead.

The sequence of attempted page visitation, signin, and redirect to destination page is a perfect job for an integration test, so let’s make one for friendly forwarding:

$ rails generate integration_test friendly_forwarding

The code then appears as in Listing 10.16.

Listing 10.16. An integration test for friendly forwarding.
spec/requests/friendly_forwardings_spec.rb
require 'spec_helper'

describe "FriendlyForwardings" do

  it "should forward to the requested page after signin" do
    user = Factory(:user)
    visit edit_user_path(user)
    # The test automatically follows the redirect to the signin page.
    fill_in :email,    :with => user.email
    fill_in :password, :with => user.password
    click_button
    # The test follows the redirect again, this time to users/edit.
    response.should render_template('users/edit')
  end
end

(As indicated by the comments, the integration test follows redirects, so testing that the response should redirect_to some URI won’t work. I learned this the hard way.)

Now for the implementation.5 In order to forward users to their intended destination, we need to store the location of the requested page somewhere, and then redirect there instead. The storage mechanism is the session facility provided by Rails, which you can think of as being like an instance of the cookies variable from Section 9.3.2 that automatically expires upon browser close.6 We also use the request object to get the fullpath, i.e., the full address of the requested page. The resulting application code appears in Listing 10.17.

Listing 10.17. Code to implement friendly forwarding.
app/helpers/sessions_helper.rb
module SessionsHelper
  .
  .
  .
  def deny_access
    store_location
    redirect_to signin_path, :notice => "Please sign in to access this page."
  end

  def redirect_back_or(default)
    redirect_to(session[:return_to] || default)
    clear_return_to
  end

  private
    .
    .
    .
    def store_location
      session[:return_to] = request.fullpath
    end

    def clear_return_to
      session[:return_to] = nil
    end
end

Here we’ve added a line to the deny_access method, first storing the full path of the request with store_location and then proceeding as before. The store_location method puts the requested URI in the session variable under the key :return_to. (We’ve made both store_location and clear_return_to private methods since they are never needed outside the Sessions helper.)

We’ve also defined the redirect_back_or method to redirect to the requested URI if it exists, or some default URI otherwise. This method is needed in the Sessions controller create action to redirect after successful signin (Listing 10.18).

Listing 10.18. The Sessions create action with friendly forwarding.
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_back_or user
    end
  end
  .
  .
  .
end

With that, the friendly forwarding integration test in Listing 10.16 should pass, and the basic user authentication and page protection implementation is complete.

10.3 Showing users

In this section, we’ll add the penultimate user action, the index action, which is designed to display all the users, not just one. Along the way, we’ll learn about populating the database with sample users and paginating the user output so that the index page can scale up to display a potentially large number of users. A mockup of the result—users, pagination links, and a “Users” navigation link—appears in Figure 10.7.7 In Section 10.4, we’ll add an administrative interface to the user index so that (presumably troublesome) users can be destroyed.

user_index_mockup
Figure 10.7: A mockup of the user index, with pagination and a “Users” nav link. (full size)

10.3.1 User index

Although we’ll keep individual user show pages visible to all site visitors, the user index will be restricted to signed-in users so that there’s a limit to how much unregistered users can see by default. Our index tests check for this, and also verify that for signed-in users all the site’s users are listed (Listing 10.19).

Listing 10.19. Tests for the user index page.
spec/controllers/users_controller_spec.rb
require 'spec_helper'

describe UsersController do
  render_views

  describe "GET 'index'" do

    describe "for non-signed-in users" do
      it "should deny access" do
        get :index
        response.should redirect_to(signin_path)
        flash[:notice].should =~ /sign in/i
      end
    end

    describe "for signed-in users" do

      before(:each) do
        @user = test_sign_in(Factory(:user))
        second = Factory(:user, :name => "Bob", :email => "another@example.com")
        third  = Factory(:user, :name => "Ben", :email => "another@example.net")

        @users = [@user, second, third]
      end

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

      it "should have the right title" do
        get :index
        response.should have_selector("title", :content => "All users")
      end

      it "should have an element for each user" do
        get :index
        @users.each do |user|
          response.should have_selector("li", :content => user.name)
        end
      end
    end
  end
  .
  .
  .
end

As you can see, the method for checking the index page is to make three factory users (signing in as the first one) and then verify that the index page has a list element (li) tag for the name of each one. Note that we’ve taken care to give the users different names so that each element in the list of users has a unique entry.

As expected, the application code uses User.all to make an @users instance variable in the index action of the Users controller (Listing 10.20).

Listing 10.20. The user index action.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_filter :authenticate, :only => [:index, :edit, :update]
  .
  .
  .
  def index
    @title = "All users"
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
    @title = @user.name
  end
  .
  .
  .
end

Note that we have added :index to the list of controllers protected by the authenticate before filter, thereby getting the first test from Listing 10.19 to pass.

To make the actual page, we need to make a view that iterates through the users and wraps each one in an li tag. We do this with the each method, displaying each user’s Gravatar and name, while wrapping the whole thing in an unordered list (ul) tag (Listing 10.21).

Listing 10.21. The user index view.
app/views/users/index.html.erb
<h1>All users</h1>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, :size => 30 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

We’ll then add a little CSS for style (Listing 10.22).

Listing 10.22. CSS for the user index.
public/stylesheets/custom.css
.
.
.
ul.users {
  margin-top: 1em;
}

.users li {
  list-style: none;
}

Finally, we’ll add a “Users” link to the site’s navigation header (Listing 10.23). This puts to use the users_path named route from Table 6.2.

Listing 10.23. A layout link to the user index.
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 "Users", users_path %></li>
      <li><%= link_to "Profile", current_user %></li>
      <li><%= link_to "Settings", edit_user_path(current_user) %></li>
      <% end %>
      .
      .
      .
    </ul>
  </nav>
</header>

With that, the user index is fully functional (with all tests passing), but it is a bit… lonely (Figure 10.8).

user_index_only_one
Figure 10.8: The user index page /users with only one user. (full size)

10.3.2 Sample users

In this section, we’ll give our lonely sample user some company. Of course, to create enough users to make a decent user index, we could use our web browser to visit the signup page and make the new users one by one, but far a better solution is to use Ruby (and Rake) to make the users for us.

First, we’ll add the Faker gem to the Gemfile, which will allow us to make sample users with semi-realistic names and email addresses (Listing 10.24).

Listing 10.24. Adding the Faker gem to the Gemfile.
source 'http://rubygems.org'
.
.
.
group :development do
  gem 'rspec-rails', '2.6.1'
  gem 'annotate', '2.4.0'
  gem 'faker', '0.3.1'
end
.
.
.

Then install as usual:

$ bundle

Next, we’ll add a Rake task to create sample users. Rake tasks live in lib/tasks, and are defined using namespaces (in this case, :db), as seen in Listing 10.25.

Listing 10.25. A Rake task for populating the database with sample users.
lib/tasks/sample_data.rake
namespace :db do
  desc "Fill database with sample data"
  task :populate => :environment do
    Rake::Task['db:reset'].invoke
    User.create!(:name => "Example User",
                 :email => "example@railstutorial.org",
                 :password => "foobar",
                 :password_confirmation => "foobar")
    99.times do |n|
      name  = Faker::Name.name
      email = "example-#{n+1}@railstutorial.org"
      password  = "password"
      User.create!(:name => name,
                   :email => email,
                   :password => password,
                   :password_confirmation => password)
    end
  end
end

This defines a task db:populate that resets the development database using db:reset (using slightly weird syntax you shouldn’t worry about too much), creates an example user with name and email address replicating our previous one, and then makes 99 more. The line

task :populate => :environment do

ensures that the Rake task has access to the local Rails environment, including the User model (and hence User.create!).

With the :db namespace as in Listing 10.25, we can invoke the Rake task as follows:

$ bundle exec rake db:populate

After running the Rake task, our application has 100 sample users, as seen in Figure 10.9. (I’ve taken the liberty of associating the first few sample addresses with photos so that they’re not all the default Gravatar image.)

user_index_all
Figure 10.9: The user index page /users with 100 sample users. (full size)

10.3.3 Pagination

Having solved the problem of too few sample users, we now encounter the opposite problem: having too many users on a page. Right now there are a hundred, which is already a reasonably large number, and on a real site it could be thousands. The solution is to paginate the users, so that (for example) only 30 show up on a page at any one time.

There are several pagination methods in Rails; we’ll use one of the simplest and most robust, called will_paginate. To use it, we need to update the Gemfile as usual (Listing 10.26). You should also restart the web server to insure that the will_paginate is loaded properly.

Listing 10.26. Including will_paginate in the Gemfile.
source 'http://rubygems.org'

gem 'rails', '3.0.11'
gem 'sqlite3', '1.3.3'
gem 'gravatar_image_tag', '1.0.0.pre2'
gem 'will_paginate', '3.0.pre2'
.
.
.

Then run bundle:

$ bundle

With will_paginate installed, we are now ready to paginate the results of finding users. We’ll start by adding the special will_paginate method in the view (Listing 10.27); we’ll see in a moment why the code appears both above and below the user list.

Listing 10.27. The user index with pagination.
app/views/users/index.html.erb
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, :size => 30 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

<%= will_paginate %>

The will_paginate method is a little magical; inside a users view, it automatically looks for an @users object, and then displays pagination links to access other pages. The view in Listing 10.27 doesn’t work yet, though, because currently @users contains the results of User.all (Listing 10.20), which is of class Array, whereas will_paginate expects an object of class WillPaginate::Collection. Happily, this is just the kind of object returned by the paginate method supplied by the will_paginate gem:

$ rails console
>> User.all.class
=> Array
>> User.paginate(:page => 1).class
=> WillPaginate::Collection

Note that paginate takes a hash argument with key :page and value equal to the page requested. User.paginate pulls the users out of the database one chunk at a time (30 by default), based on the :page parameter. So, for example, page 1 is users 1–30, page 2 is users 31–60, etc.

We can paginate the users in the sample application by using paginate in place of all in the index action (Listing 10.28). Here the :page parameter comes from params[:page], which is generated automatically by will_paginate.

Listing 10.28. Paginating the users in the index action.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_filter :authenticate, :only => [:index, :edit, :update]
  .
  .
  .
  def index
    @title = "All users"
    @users = User.paginate(:page => params[:page])
  end
  .
  .
  .
end

The user index page should now be working, appearing as in Figure 10.10. (On some systems, you may have to restart the Rails server at this point.) Because we included will_paginate both above and below the user list, the pagination links appear in both places.

user_index_pagination_rails_3
Figure 10.10: The user index page /users with pagination. (full size)

If you now click on either the 2 link or Next link, you’ll get the second page of results, as shown in Figure 10.11.

user_index_page_two_rails_3
Figure 10.11: Page 2 of the user index (/users?page=2). (full size)

Testing pagination

Testing pagination requires detailed knowledge of how will_paginate works, so we did the implementation first, but it’s still a good idea to test it. To do this, we need to invoke pagination in a test, which means making more than 30 (factory) users.

As before, we’ll use Factory Girl to simulate users, but immediately we have a problem: user email addresses must be unique, which would appear to require creating more than 30 users by hand—a terribly cumbersome job. Fortunately, Factory Girl anticipates this issue, and provides sequences to solve it, as shown in Listing 10.29.

Listing 10.29. Defining a Factory Girl sequence.
spec/factories.rb
Factory.define :user do |user|
  user.name                  "Michael Hartl"
  user.email                 "mhartl@example.com"
  user.password              "foobar"
  user.password_confirmation "foobar"
end

Factory.sequence :name do |n|
  "Person #{n}"
end

Factory.sequence :email do |n|
  "person-#{n}@example.com"
end

This arranges to return email addresses like person-1@example.com, person-2@example.com, etc., which we invoke using the next method:

Factory(:user, :email => Factory.next(:email))

Note that Listing 10.29 also adds an analogous sequence to create distinct user names.

Applying the idea of factory sequences, we can make 31 users (the original @user plus 30 more) inside a test, and then verify that the response has the HTML expected from will_paginate (which you should be able to determine using Firebug or by viewing the page source). The result appears in Listing 10.30.

Listing 10.30. A test for pagination.
spec/controllers/users_controller_spec.rb
require 'spec_helper'

describe "UsersController" do
  render_views

  describe "GET 'index'" do
    .
    .
    .
    describe "for signed-in users" do

      before(:each) do
        .
        .
        .
        @users = [@user, second, third]
        30.times do
          @users << Factory(:user, :name => Factory.next(:name),
                                   :email => Factory.next(:email))
        end
      end
      .
      .
      .
      it "should have an element for each user" do
        get :index
        @users[0..2].each do |user|
          response.should have_selector("li", :content => user.name)
        end
      end

      it "should paginate users" do
        get :index
        response.should have_selector("div.pagination")
        response.should have_selector("span.disabled", :content => "Previous")
        response.should have_selector("a", :href => "/users?page=2",
                                           :content => "2")
        response.should have_selector("a", :href => "/users?page=2",
                                           :content => "Next")
      end
    end
  end
  .
  .
  .
end

This code ensures that the tests invoke pagination by adding 308 users to the @users variable using the Array push notation <<, which appends an element to an existing array:

$ rails console
>> a = [1, 2, 5]
=> [1, 2, 5]
>> a << 17
=> [1, 2, 5, 17]
>> a << 42 << 1337
=> [1, 2, 5, 17, 42, 1337]

We see from the last example that occurrences of << can be chained. In the test itself, note the compact notation have_selector("div.pagination"), which borrows the class convention from CSS (first seen in Listing 5.3) to check for a div tag with class pagination. Also note that, since there are now 33 users, we’ve updated the user element test to use only the first three elements ([0..2]) of the @users array, which is what we had before in Listing 10.19:

@users[0..2].each do |user|
  response.should have_selector("li", :content => user.name)
end

With that, our pagination code is well-tested, and there’s only one minor detail left, as we’ll see in the next section.

10.3.4 Partial refactoring

The paginated user index is now complete, but there’s one improvement I can’t resist including: Rails has some incredibly slick tools for making compact views, and in this section we’ll refactor the index page to use them. Because our code is well-tested, we can refactor with confidence, assured that we are unlikely to break our site’s functionality.

The first step in our refactoring is to replace the user li from Listing 10.27 with a render call (Listing 10.31).

Listing 10.31. The first refactoring attempt at the index view.
app/views/users/index.html.erb
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <%= render user %>
  <% end %>
</ul>

<%= will_paginate %>

Here we call render not on a string with the name of a partial, but rather on a user variable of class User;9 in this context, Rails automatically looks for a partial called _user.html.erb, which we must create (Listing 10.32).

Listing 10.32. A partial to render a single user.
app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, :size => 30 %>
  <%= link_to user.name, user %>
</li>

This is a definite improvement, but we can do even better: we can call render directly on the @users variable (Listing 10.33).

Listing 10.33. The fully refactored user index.
app/views/users/index.html.erb
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <%= render @users %>
</ul>

<%= will_paginate %>

Here Rails infers that @users is a list of User objects; moreover, when called with a collection of users, Rails automatically iterates through them and renders each one with the _user.html.erb partial. The result is the impressively compact code in Listing 10.33.

10.4 Destroying users

Now that the user index is complete, there’s only one canonical REST action left: destroy. In this section, we’ll add links to delete users, as mocked up in Figure 10.12, and define the destroy action necessary to accomplish the deletion. But first, we’ll create the class of administrative users authorized to do so.

user_index_delete_links_mockup
Figure 10.12: A mockup of the user index with delete links. (full size)

10.4.1 Administrative users

We will identify privileged administrative users with a boolean admin attribute in the User model, which will lead to an admin? method to test for admin status. We can write tests for this attribute as in Listing 10.34.

Listing 10.34. Tests for an admin attribute.
spec/models/user_spec.rb
.
.
.
  describe "admin attribute" do

    before(:each) do
      @user = User.create!(@attr)
    end

    it "should respond to admin" do
      @user.should respond_to(:admin)
    end

    it "should not be an admin by default" do
      @user.should_not be_admin
    end

    it "should be convertible to an admin" do
      @user.toggle!(:admin)
      @user.should be_admin
    end
  end
end

Here we’ve used the toggle! method to flip the admin attribute from false to true. Also note that the line

@user.should be_admin

implies (via the RSpec boolean convention) that the user should have an admin? boolean method.

We add the admin attribute with a migration as usual, indicating the boolean type on the command line:

$ rails generate migration add_admin_to_users admin:boolean

The migration simply adds the admin column to the users table (Listing 10.35), yielding the data model in Figure 10.13.

Listing 10.35. The migration to add a boolean admin attribute to users.
db/migrate/<timestamp>_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :admin, :boolean, :default => false
  end

  def self.down
    remove_column :users, :admin
  end
end

Note that we’ve added the argument :default => false to add_column in Listing 10.35, which means that users will not be administrators by default. (Without the :default => false argument, admin will be nil by default, which is still false, so this step is not strictly necessary. It is more explicit, though, and communicates our intentions more clearly both to Rails and to readers of our code.)

user_model_admin
Figure 10.13: The User model with an added admin boolean attribute.

Finally, we migrate the development database and prepare the test database:

$ bundle exec rake db:migrate
$ bundle exec rake db:test:prepare

As expected, Rails figures out the boolean nature of the admin attribute and automatically adds the question-mark method admin?:10

$ rails console
>> user = User.first
>> user.admin?
=> false
>> user.password = "foobar"
>> user.toggle!(:admin)
=> true
>> user.admin?
=> true

As a final step, let’s update our sample data populator to make the first user an admin (Listing 10.36).

Listing 10.36. The sample data populator code with an admin user.
lib/tasks/sample_data.rake
namespace :db do
  desc "Fill database with sample data"
  task :populate => :environment do
    Rake::Task['db:reset'].invoke
    admin = User.create!(:name => "Example User",
                         :email => "example@railstutorial.org",
                         :password => "foobar",
                         :password_confirmation => "foobar")
    admin.toggle!(:admin)
    .
    .
    .
  end
end

Finally, re-run the populator to reset the database and then rebuild it from scratch:

$ bundle exec rake db:populate

Revisiting attr_accessible

You might have noticed that Listing 10.36 makes the user an admin with toggle!(:admin), but why not just add :admin => true to the initialization hash? The answer is, it won’t work, and this is by design: only attr_accessible attributes can be assigned through mass assignment, and the admin attribute isn’t accessible. Listing 10.37 reproduces the most recent list of attr_accessible attributes—note that :admin is not on the list.

Listing 10.37. The attr_accessible attributes for the User model without an :admin attribute.
app/models/user.rb
class User < ActiveRecord::Base
  attr_accessor :password
  attr_accessible :name, :email, :password, :password_confirmation
  .
  .
  .
end

Explicitly defining accessible attributes is crucial for good site security. If we omitted the attr_accessible list in the User model (or foolishly added :admin to the list), a malicious user could send a PUT request as follows:11

put /users/17?admin=1

This request would make user 17 an admin, which could be a potentially serious security breach, to say the least. Because of this danger, it is a good practice to define attr_accessible for every model.

10.4.2 The destroy action

The final step needed to complete the Users resource is to add delete links and a destroy action. We’ll start by adding a delete link for each user on the user index page (Listing 10.38).

Listing 10.38. User delete links (viewable only by admins).
app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, :size => 30 %>
  <%= link_to user.name, user %>
  <% if current_user.admin? %>
  | <%= link_to "delete", user, :method => :delete, :confirm => "You sure?",
                                :title => "Delete #{user.name}" %>
  <% end %>
</li>

Note the :method => :delete argument, which arranges for the link to issue the necessary DELETE request. We’ve also wrapped each link inside an if statement so that only admins can see them. The result for our admin user appears in Figure 10.14.

Web browsers can’t send DELETE requests natively, so Rails fakes them with JavaScript.12 To get the delete links to work, we therefore have to include the default Rails JavaScript libraries, which we do by adding the line

<%= javascript_include_tag :defaults %>

to the site layout. The result is shown in Listing 10.39.

Listing 10.39. Adding the default JavaScript libraries to the sample app.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <%= csrf_meta_tag %>
    <%= render 'layouts/stylesheets' %>
    <%= javascript_include_tag :defaults %>
  </head>
  <body>
    .
    .
    .
  </body>
</html>
index_delete_links_rails_3
Figure 10.14: The user index /users with delete links. (full size)

Even though only admins can see the delete links, there’s still a terrible security hole: any sufficiently sophisticated attacker could simply issue DELETE requests from the command line and delete any user on the site. To secure the site properly, we also need access control, so our tests should check not only that admins can delete users, but also that other users can’t. The results appear in Listing 10.40. Note that, in analogy with the get, post, and put methods, we use delete to issue DELETE requests inside of tests.

Listing 10.40. Tests for destroying users.
spec/controllers/users_controller_spec.rb
describe UsersController do
  render_views
  .
  .
  .
  describe "DELETE 'destroy'" do

    before(:each) do
      @user = Factory(:user)
    end

    describe "as a non-signed-in user" do
      it "should deny access" do
        delete :destroy, :id => @user
        response.should redirect_to(signin_path)
      end
    end

    describe "as a non-admin user" do
      it "should protect the page" do
        test_sign_in(@user)
        delete :destroy, :id => @user
        response.should redirect_to(root_path)
      end
    end

    describe "as an admin user" do

      before(:each) do
        admin = Factory(:user, :email => "admin@example.com", :admin => true)
        test_sign_in(admin)
      end

      it "should destroy the user" do
        lambda do
          delete :destroy, :id => @user
        end.should change(User, :count).by(-1)
      end

      it "should redirect to the users page" do
        delete :destroy, :id => @user
        response.should redirect_to(users_path)
      end
    end
  end
end

(You might notice that we’ve set an admin user using :admin => true; user factories are not bound by the rules of attr_accessible parameters.) Note here that the change method can take a negative value, which means that, just as we verified user creation by testing for a change of +1 (Listing 8.14), we can verify user destruction by testing for a change of -1:

lambda do
  delete :destroy, :id => @user
end.should change(User, :count).by(-1)

As you might suspect by now, the implementation uses a before filter, this time to restrict access to the destroy action to admins. The destroy action itself finds the user, destroys it, and then redirects to user index (Listing 10.41).

Listing 10.41. A before filter restricting the destroy action to admins.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_filter :authenticate, :only => [:index, :edit, :update, :destroy]
  before_filter :correct_user, :only => [:edit, :update]
  before_filter :admin_user,   :only => :destroy
  .
  .
  .
  def destroy
    User.find(params[:id]).destroy
    flash[:success] = "User destroyed."
    redirect_to users_path
  end

  private
    .
    .
    .
    def admin_user
      redirect_to(root_path) unless current_user.admin?
    end
end

Note that the destroy action uses method chaining (seen briefly in Section 4.2.3) in the line

User.find(params[:id]).destroy

which saves a line of code.

At this point, all the tests should be passing, and the Users resource—with its controller, model, and views—is functionally complete.

10.5 Conclusion

We’ve come a long way since introducing the Users controller way back in Section 5.3. Those users couldn’t even sign up; now users can sign up, sign in, sign out, view their profiles, edit their settings, and see an index of all users—and some can even destroy other users.

The rest of this book builds on the foundation of the Users resource (and associated authentication system) to make a site with Twitter-like microposts (Chapter 11) and user following (Chapter 12). These chapters will introduce some of the most powerful features of Rails, including data modeling with has_many and has_many :through.

Before moving on, be sure to merge all the changes into the master branch:

$ git add .
$ git commit -m "Finish user edit, update, index, and destroy actions"
$ git checkout master
$ git merge updating-users

It’s also worth noting that this chapter saw the last of the necessary gem installations. For reference, the final Gemfile is shown in Listing 10.42.

Listing 10.42. The final Gemfile for the sample application.
source 'http://rubygems.org'

gem 'rails', '3.0.11'
gem 'sqlite3', '1.3.3'
gem 'gravatar_image_tag', '1.0.0.pre2'
gem 'will_paginate', '3.0.pre2'

group :development do
  gem 'rspec-rails', '2.6.1'
  gem 'annotate', '2.4.0'
  gem 'faker', '0.3.1'
end

group :test do
  gem 'rspec-rails', '2.6.1'
  gem 'webrat', '0.7.1'
  gem 'spork', '0.9.0.rc8'
  gem 'factory_girl_rails', '1.0'
end

10.6 Exercises

  1. Arrange for the Gravatar “change” link in Listing 10.3 to open in a new window (or tab). Hint: Search the web; you should find one particularly robust method involving something called _blank.
  2. Remove the duplicated form code by refactoring the new.html.erb and edit.html.erb views to use the partial in Listing 10.43. Note that you will have to pass the form variable f explicitly as a local variable, as shown in Listing 10.44.
  3. Signed-in users have no reason to access the new and create actions in the Users controller. Arrange for such users to be redirected to the root url if they do try to hit those pages.
  4. Add tests to check that the delete links in Listing 10.38 appear for admins but not for normal users.
  5. Modify the destroy action to prevent admin users from destroying themselves. (Write a test first.)
Listing 10.43. A partial for the new and edit form fields.
app/views/users/_fields.html.erb
<%= render 'shared/error_messages', :object => f.object %>
<div class="field">
  <%= f.label :name %><br />
  <%= f.text_field :name %>
</div>
<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="field">
  <%= f.label :password_confirmation, "Confirmation" %><br />
  <%= f.password_field :password_confirmation %>
</div>
Listing 10.44. The new user view with partial.
app/views/users/new.html.erb
<h1>Sign up</h1>

<%= form_for(@user) do |f| %>
  <%= render 'fields', :f => f %>
  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>
  1. Image from http://www.flickr.com/photos/sashawolff/4598355045/
  2. The Gravatar site actually redirects this to http://en.gravatar.com/emails, which is for English language users, but I’ve omitted the en part to account for the use of other languages. 
  3. Image from http://www.flickr.com/photos/sashawolff/4598355045/
  4. Don’t worry about how this works; the details are of interest to developers of the Rails framework itself, but by design are not important for Rails application developers. 
  5. The code in this section is adapted from the Clearance gem by thoughtbot
  6. Indeed, as noted in Section 9.6, session is implemented in just this way. 
  7. Baby photo from http://www.flickr.com/photos/glasgows/338937124/
  8. Technically, we only need to create 28 additional factory users since we already have three, but I find the meaning clearer if we create 30 instead. 
  9. The name user is immaterial—we could have written @users.each do |foobar| and then used render foobar. The key is the class of the object—in this case, User
  10. The toggle! method invokes the Active Record callbacks but not the validations, so we have to set the password attribute (but not the confirmation) in order to have a non-blank password in the encrypt_password callback. 
  11. Command-line tools such as curl (seen in Box 3.2) can issue PUT requests of this form. 
  12. This means that the delete links won’t work if the user has JavaScript disabled. If you must support non-JavaScript-enabled browsers you can fake a DELETE request using a form and a POST request, which works even without JavaScript; see the Railscast on “Destroy Without JavaScript” for details.