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. Install Git
        2. Install Ruby
        3. Install RubyGems
        4. 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

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 6 Modeling and viewing users, part I

In Chapter 5, we ended with a stub page for creating new users (Section 5.3); over the course of the next three chapters, we’ll fulfill the promise implicit in this incipient signup page. The first critical step is to create a data model for users of our site, together with a way to store that data. Completing this task is the goal for this chapter and the next (Chapter 7), and we’ll give users the ability to sign up in Chapter 8. Once the sample application can create new users, we’ll let them sign in and sign out (Chapter 9), and in Chapter 10 (Section 10.2) we’ll learn how to protect pages from improper access.

Taken together, the material in Chapter 6 through Chapter 10 develops a full Rails login and authentication system. As you may know, there are various pre-built authentication solutions out there in Rails land; Box 6.1 explains why (at least at first) it’s a good idea to roll your own.

In parallel with our data modeling, we’ll also develop a web page for showing users, which will serve as the first step toward implementing the REST architecture for users (discussed briefly in Section 2.2.2). Though we won’t get very far in this chapter, our eventual goal for the user profile pages is to show the user’s profile image, basic user data, and a list of microposts, as mocked up in Figure 6.1.1 (Figure 6.1 has our first example of lorem ipsum text, which has a fascinating story that you should definitely read about some time.) In this chapter, we’ll lay the essential foundation for the user show page, and we’ll start filling in the details starting in Chapter 7.

profile_mockup
Figure 6.1: A mockup of our best guess at the user show page. (full size)

As usual, if you’re following along using Git for version control, now would be a good time to make a topic branch for modeling users:

$ git checkout master
$ git checkout -b modeling-users

(The first line here is just to make sure that you start on the master branch, so that the modeling-users topic branch is based on master. You can skip that command if you’re already on the master branch.)

6.1 User model

Although the ultimate goal of the next three chapters is to make a signup page for our site, it would do little good to accept signup information now, since we don’t currently have any place to put it. Thus, the first step in signing up users is to make a data structure to capture and store their information. In Rails, the default data structure for a data model is called, naturally enough, a model (the M in MVC from Section 1.2.6). The default Rails solution to the problem of persistence is to use a database for long-term data storage, and the default library for interacting with the database is called Active Record.2

Active Record comes with a host of methods for creating, saving, and finding data objects, all without having to use the structured query language (SQL)3 used by relational databases. Moreover, Rails has a feature called migrations to allow data definitions to be written in pure Ruby, without having to learn an SQL data definition language (DDL).4 The effect is that Rails insulates you almost entirely from the details of the data store. In this book, by using SQLite for development and Heroku for deployment (Section 1.4), we have developed this theme even further, to the point where we barely ever have to think about how Rails stores data, even for production applications.5

6.1.1 Database migrations

You may recall from Section 4.4.5 that we have already encountered, via a custom-built User class, user objects with name and email attributes. That class served as a useful example, but it lacked the critical property of persistence: when we created a User object at the Rails console, it disappeared as soon as we exited. Our goal in this section is to create a model for users that won’t disappear quite so easily.

As with the User class in Section 4.4.5, we’ll start by modeling a user with two attributes, a name and an email address, the latter of which we’ll use as a unique username.6 (We’ll add a password attribute in Section 7.1.) In Listing 4.8, we did this with Ruby’s attr_accessor keyword:

class User
  attr_accessor :name, :email
  .
  .
  .
end

In contrast, when using Rails to model users we don’t need to identify the attributes explicitly. As noted briefly above, to store data Rails uses a relational database by default, which consists of tables composed of data rows, where each row has columns of data attributes. For example, to store users with names and email addresses, we’ll create a users table with name and email columns (with each row corresponding to one user). By naming the columns in this way, we’ll let Active Record figure out the User object attributes for us.

Let’s see how this works. (If this discussion gets too abstract for your taste, be patient; the console examples starting in Section 6.1.3 and the database browser screenshots in Figure 6.3 and Figure 6.8 should make things clearer.) In Section 5.3.1, recall (Listing 5.23) that we created a Users controller (along with a new action) using the command

$ rails generate controller Users new

There is an analogous command for making a model: generate model; Listing 6.1 shows the command to generate a User model with two attributes, name and email.

Listing 6.1. Generating a User model.
$ rails generate model User name:string email:string
      invoke  active_record
      create    db/migrate/<timestamp>_create_users.rb
      create    app/models/user.rb
      invoke    rspec
      create      spec/models/user_spec.rb

(Note that, in contrast to the plural convention for controller names, model names are singular: a Users controller, but a User model.) By passing the optional parameters name:string and email:string, we tell Rails about the two attributes we want, along with what types those attributes should be (in this case, string). Compare this with including the action names in Listing 3.4 and Listing 5.23.

One of the results of the generate command in Listing 6.1 is a new file called a migration. Migrations provide a way to alter the structure of the database incrementally, so that our data model can adapt to changing requirements. In the case of the User model, the migration is created automatically by the model generation script; it creates a users table with two columns, name and email, as shown in Listing 6.2. (We’ll see in Section 6.2.4 how to make a migration from scratch.)

Listing 6.2. Migration for the User model (to create a users table).
db/migrate/<timestamp>_create_users.rb
class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :name
      t.string :email

      t.timestamps
    end
  end

  def self.down
    drop_table :users
  end
end

Note that the name of the migration is prefixed by a timestamp based on when the migration was generated. In the early days of migrations, the filenames were prefixed with incrementing integers, which caused conflicts for collaborating teams if multiple programmers had migrations with the same number. Barring highly improbable millisecond-level simultaneity, using timestamps conveniently avoids such collisions.

Let’s focus on the self.up method, which uses a Rails method called create_table to create a table in the database for storing users. (The use of self in self.up identifies it as a class method. This doesn’t matter now, but we’ll learn about class methods when we make one of our own in Section 7.2.4.) The create_table method accepts a block (Section 4.3.2) with one block variable, in this case called t (for “table”). Inside the block, the create_table method uses the t object to create name and email columns in the database, both of type string.7 Here the table name is plural (users) even though the model name is singular (User), which reflects a linguistic convention followed by Rails: a model represents a single user, whereas a database table consists of many users. The final line in the block, t.timestamps, is a special command that creates two magic columns called created_at and updated_at, which are timestamps that automatically record when a given user is created and updated. (We’ll see concrete examples of the magic columns starting in Section 6.1.3.) The full data model represented by this migration is shown in Figure 6.2.

user_model_initial
Figure 6.2: The users data model produced by Listing 6.2.

We can run the migration, known as “migrating up”, using the rake command (Box 2.1) as follows:8

$ rake db:migrate

(You may recall that we have run this command before, in Section 1.2.5 and again in Chapter 2.) The first time db:migrate is run, it creates a file called db/development.sqlite3, which is an SQLite9 database. We can see the structure of the database using the excellent SQLite Database Browser to open the db/development.sqlite3 file (Figure 6.3); compare with the diagram in Figure 6.2. You might note that there’s one column in Figure 6.3 not accounted for in the migration: the id column. As noted briefly in Section 2.2, this column is created automatically, and is used by Rails to identify each row uniquely.

sqlite_database_browser
Figure 6.3: The SQLite Database Browser with our new users table. (full size)

You’ve probably inferred that running db:migrate executes the self.up command in the migration file. What, then, of self.down? As you might guess, down migrates down, reversing the effects of migrating up. In our case, this means dropping the users table from the database:

class CreateUsers < ActiveRecord::Migration
  .
  .
  .
  def self.down
    drop_table :users
  end
end

You can execute down with rake using the argument db:rollback:

$ rake db:rollback

This is often useful if you realize there’s another column you want to add but don’t want the trouble of making a new migration: you can roll back the migration, add the desired column, and then migrate back up. (This isn’t always convenient, and we’ll learn how to add columns to an existing table in Section 7.1.2.)

If you rolled back the database, migrate up again before proceeding:

$ rake db:migrate

6.1.2 The model file

We’ve seen how the User model generation in Listing 6.1 generated a migration file (Listing 6.2), and we saw in Figure 6.3 the results of running this migration: it updated a file called development.sqlite3 by creating a table users with columns id, name, email, created_at, and updated_at. Listing 6.1 also created the model itself; the rest of this section is dedicated to understanding it.

We begin by looking at the code for the User model, which lives in the file user.rb inside the app/models/ directory; it is, to put it mildly, very compact (Listing 6.3).

Listing 6.3. The brand new User model.
app/models/user.rb
class User < ActiveRecord::Base
end

Recall from Section 4.4.2 that the syntax class User < ActiveRecord::Base means that the User class inherits from ActiveRecord::Base, so that the User model automatically has all the functionality of the ActiveRecord::Base class. Of course, knowledge of this inheritance doesn’t do any good unless we know what ActiveRecord::Base contains, and we’ll get a first taste starting momentarily. Before we move on, though, there are two tasks to complete.

Model annotation

Though it’s not strictly necessary, you might find it convenient to annotate your Rails models using the annotate-models gem (Listing 6.4).

Listing 6.4. Adding the annotate-models gem to the Gemfile.
source 'http://rubygems.org'
.
.
.
group :development do
  gem 'rspec-rails', '2.0.0.beta.18'
  gem 'annotate-models', '1.0.4'
end

group :test do
  .
  .
  .
end

(We place the annotate-models gem in a group :development block (analogous to group :test) because the annotations aren’t needed in production applications.) We next install it with bundle:

$ bundle install

This gives us a command called annotate, which simply adds comments containing the data model to the model file:

$ annotate
Annotated User

The results appear in Listing 6.5.

Listing 6.5. The annotated User model.
app/models/user.rb
# == Schema Information
# Schema version: <timestamp>
#
# Table name: users
#
#  id         :integer         not null, primary key
#  name       :string(255)
#  email      :string(255)
#  created_at :datetime
#  updated_at :datetime
#

class User < ActiveRecord::Base
end

I find that having the data model visible in the model files is useful for reminding me which attributes the model has, but future code listings will usually omit the annotations for brevity.

Accessible attributes

Another step that isn’t strictly necessary but is a really good idea is to tell Rails which attributes of the model are accessible, i.e., which attributes can be modified by outside users (such as users submitting requests with web browsers). We do this with the attr_accessible method (Listing 6.6). We’ll see in Chapter 10 that using attr_accessible is important for preventing a mass assignment vulnerability, a distressingly common and often serious security hole in many Rails applications.

Listing 6.6. Making the name and email attributes accessible.
app/models/user.rb
class User < ActiveRecord::Base
  attr_accessible :name, :email
end

6.1.3 Creating user objects

We’ve done some good prep work, and now it’s time to cash in and learn about Active Record by playing with our newly created User model. As in Chapter 4, our tool of choice is the Rails console. Since we don’t (yet) want to make any changes to our database, we’ll start the console in a sandbox:

$ rails console --sandbox
Loading development environment in sandbox (Rails 3.0.0)
Any modifications you make will be rolled back on exit
>> 

As indicated by the helpful message “Any modifications you make will be rolled back on exit”, when started in a sandbox the console will “roll back” (i.e., undo) any database changes introduced during the session.

When working at the console, it’s useful to keep an eye on the development log, which records the actual low-level SQL statements being issued by Active Record, as shown in Figure 6.4. The way to get this output at a Unix command line is to tail the log:

$ tail -f log/development.log

The -f flag ensures that tail will display additional lines as they are written. I recommend keeping an open terminal window for tailing the log whenever working at the console.

development_log
Figure 6.4: Tailing the development log. (full size)

In the console session in Section 4.4.5, we created a new user object with User.new, which we had access to only after requiring the example user file in Listing 4.8. With models, the situation is different; as you may recall from Section 4.4.4, the Rails console automatically loads the Rails environment, which includes the models. This means that we can make a new user object without any further work:

>> User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>

We see here the default console representation of a user object, which prints out the same attributes shown in Figure 6.3 and Listing 6.5.

When called with no arguments, User.new returns an object with all nil attributes. In Section 4.4.5, we designed the example User class to take an initialization hash to set the object attributes; that design choice was motivated by Active Record, which allows objects to be initialized in the same way:

>> user = User.new(:name => "Michael Hartl", :email => "mhartl@example.com")
#<User id: nil, name: "Michael Hartl", email: "mhartl@example.com",
created_at: nil, updated_at: nil>

Here we see that the name and email attributes have been set as expected.

If you’ve been tailing the development log, you may have noticed that no new lines have shown up yet. This is because calling User.new doesn’t touch the database; it simply creates a new Ruby object in memory. To save the user object to the database, we call the save method on the user variable:

>> user.save
=> true

The save method returns true if it succeeds and false otherwise. (Currently, all saves should succeed; we’ll see cases in Section 6.2 when some will fail.) As soon as you save, you should see a line in the development log with the SQL command to INSERT INTO "users". Because of the many methods supplied by Active Record, we won’t ever need raw SQL in this book, and I’ll omit discussion of the SQL commands from now on. But you can learn a lot by watching the log.

You may have noticed that the new user object had nil values for the id and the magic columns created_at and updated_at attributes. Let’s see if our save changed anything:

>> user
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">

We see that the id has been assigned a value of 1, while the magic columns have been assigned the current time and date.10 Currently the created and updated timestamps are identical; we’ll see them differ in Section 6.1.5.

As with the User class in Section 4.4.5, instances of the User model allow access to their attributes using a dot notation:11

>> user.name
=> "Michael Hartl"
>> user.email
=> "mhartl@example.com"
>> user.updated_at
=> Tue, 05 Jan 2010 00:57:46 UTC +00:00

As we’ll see in Chapter 8, it’s often convenient to make and save a model in two steps as we have above, but Active Record also lets you combine them into one step with User.create:

>> User.create(:name => "A Nother", :email => "another@example.org")
=> #<User id: 2, name: "A Nother", email: "another@example.org", created_at:
"2010-01-05 01:05:24", updated_at: "2010-01-05 01:05:24">
>> foo = User.create(:name => "Foo", :email => "foo@bar.com")
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2010-01-05
01:05:42", updated_at: "2010-01-05 01:05:42">

Note that User.create, rather than returning true or false, returns the User object itself, which we can optionally assign to a variable (such as foo in the second command above).

The inverse of create is destroy:

>> foo.destroy
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2010-01-05
01:05:42", updated_at: "2010-01-05 01:05:42">

Oddly, destroy, like create, returns the object in question, though I can’t recall ever having used the return value of destroy. Even odder, perhaps, is that the destroyed object still exists in memory:

>> foo
=> #<User id: 3, name: "Foo", email: "foo@bar.com", created_at: "2010-01-05
01:05:42", updated_at: "2010-01-05 01:05:42">

How do we know if we really destroyed an object? And for saved and non-destroyed objects, how can we retrieve users from the database? It’s time to learn how to use Active Record to find user objects.

6.1.4 Finding user objects

Active Record provides several options for finding objects. Let’s use them to find the first user we created while verifying that the third user (foo) has been destroyed. We’ll start with the existing user:

>> User.find(1)
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">

Here we’ve passed the id of the user to User.find; Active Record returns the user with that id attribute.

Let’s see if the user with an id of 3 still exists in the database:

>> User.find(3)
ActiveRecord::RecordNotFound: Couldn't find User with ID=3

Since we destroyed our third user in Section 6.1.3, Active Record can’t find it in the database. Instead, find raises an exception, which is a way of indicating an exceptional event in the execution of a program—in this case, a nonexistent Active Record id, which causes find to raise an ActiveRecord::RecordNotFound exception.12

In addition to the generic find, Active Record also allows us to find users by specific attributes:

>> User.find_by_email("mhartl@example.com")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">

Since we will be using email addresses as usernames, this sort of find will be useful when we learn how to let users sign in to our site (Chapter 8).13

We’ll end with a couple of more general ways of finding users. First, there’s first:

>> User.first
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">

Naturally, first just returns the first user in the database. There’s also all:

>> User.all
=> [#<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">,
#<User id: 2, name: "A Nother", email: "another@example.org", created_at:
"2010-01-05 01:05:24", updated_at: "2010-01-05 01:05:24">]

No prizes for inferring that all returns an array (Section 4.3.1) of all users in the database.

6.1.5 Updating user objects

Once we’ve created objects, we often want to update them. There are two basic ways to do this. First, we can assign attributes individually, as we did in Section 4.4.5:

>> user           # Just a reminder about our user's attributes
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-05 00:57:46", updated_at: "2010-01-05 00:57:46">
>> user.email = "mhartl@example.net"
=> "mhartl@example.net"
>> user.save
=> true

Note that the final step is necessary to write the changes to the database. We can see what happens without a save by using reload, which reloads the object based on the database information:

>> user.email
=> "mhartl@example.net"
>> user.email = "foo@bar.com"
=> "foo@bar.com"
>> user.reload.email
=> "mhartl@example.net"

Now that we’ve updated the user, the magic columns differ, as promised in Section 6.1.3:

>> user.created_at
=> "2010-01-05 00:57:46"
>> user.updated_at
=> "2010-01-05 01:37:32"

The second way to update attributes is to use update_attributes:

>> user.update_attributes(:name => "The Dude", :email => "dude@abides.org")
=> true
>> user.name
=> "The Dude"
>> user.email
=> "dude@abides.org"

The update_attributes method accepts a hash of attributes, and on success performs both the update and the save in one step (returning true to indicate that the save went through). It’s worth noting that, once you have defined some attributes as accessible using attr_accessible (Section 6.1.2.2), only those attributes can be modified using update_attributes. If you ever find that your models mysteriously start refusing to update certain columns, check to make sure that those columns are included in the call to attr_accessible.

6.2 User validations

The User model we created in Section 6.1 now has working name and email attributes, but they are completely generic: any string (including an empty one) is currently valid in either case. And yet, names and email addresses are more specific than this. For example, name should be non-blank, and email should match the specific format characteristic of email addresses. Moreover, since we’ll be using email addresses as unique usernames when users sign in, we shouldn’t allow email duplicates in the database.

In short, we shouldn’t allow name and email to be just any strings; we should enforce certain constraints on their values. Active Record allows us to impose such constraints using validations. In this section we’ll cover several of the most common cases, validating presence, length, format and uniqueness. In Section 7.1.1 we’ll add a final common validation, confirmation. And we’ll see in Section 8.2 how validations give us convenient error messages when users make submissions that violate them.

As with the other features of our sample app, we’ll add User model validations using test-driven development. Since we’ve changed the data model, it’s a good idea to prepare the test database before proceeding:

$ rake db:test:prepare

This just ensures that the data model from the development database, db/development.sqlite3, is reflected in the test database, db/test.sqlite3.

6.2.1 Validating presence

We’ll start with a test for the presence of a name attribute. Although the first step in TDD is to write a failing test (Section 3.2.2), in this case we don’t yet know enough about validations to write the proper test, so we’ll write the validation first, using the console to understand it. Then we’ll comment out the validation, write a failing test, and verify that uncommenting the validation gets the test to pass. This procedure may seem pedantic for such a simple test, but I have seen14 many “simple” tests that test the wrong thing; being meticulous about TDD is simply the only way to be confident that we’re testing the right thing. (This comment-out technique is also useful when rescuing an application whose application code is already written but—quelle horreur!—has no tests.)

The way to validate the presence of the name attribute is to use the validates method with argument :presence => true, as shown in Listing 6.7. The :presence => true argument is a one-element options hash; recall from Section 4.3.4 that curly braces are optional when passing hashes as the final argument in a method. (As noted in Section 5.1.1, the use of options hashes is a recurring theme in Rails.)

Listing 6.7. Validating the presence of a name attribute.
app/models/user.rb
class User < ActiveRecord::Base 
  attr_accessible :name, :email

  validates :name, :presence => true
end

As discussed briefly in Section 2.3.2, the use of validates is characteristic of Rails 3. (In Rails 2.3, we would write validates_presence_of :name instead.)

Listing 6.7 may look like magic, but validates is just a method, as indeed is attr_accessible. An equivalent formulation of Listing 6.7 using parentheses is as follows:

class User < ActiveRecord::Base 
  attr_accessible(:name, :email)

  validates(:name, :presence => true)
end

Let’s drop into the console to see the effects of adding a validation to our User model:15

$ rails console --sandbox
>> user = User.new(:name => "", :email => "mhartl@example.com")
>> user.save
=> false
>> user.valid?
=> false

Here user.save returns false, indicating a failed save. In the final command, we use the valid? method, which returns false when the object fails one or more validations, and true when all validations pass. (Recall from Section 4.2.3 that Ruby uses a question mark to indicate such true/false boolean methods.) In this case, we only have one validation, so we know which one failed, but it can still be helpful to check using the errors object generated on failure:

>> user.errors.full_messages
=> ["Name can't be blank"]

(The error message is a hint that Rails validates the presence of an attribute using the blank? method, which we saw at the end of Section 4.4.2.)

Now for the failing test. To ensure that our incipient test will fail, let’s comment out the validation at this point (Listing 6.8).

Listing 6.8. Commenting out a validation to ensure a failing test.
app/models/user.rb
class User < ActiveRecord::Base 
  attr_accessible :name, :email

  # validates :name, :presence => true
end

As in the case of controller generation (e.g., Listing 5.23), the model generate command in Listing 6.1 produces an initial spec for testing users, but in this case it’s practically blank (Listing 6.9).

Listing 6.9. The practically blank default User spec.
spec/models/user_spec.rb
require 'spec_helper'

describe User do
  pending "add some examples to (or delete) #{__FILE__}"
end

This simply uses the pending method to indicate that we should fill the spec with something useful. We can see its effect by running the User model spec:

$ rspec spec/models/user_spec.rb 
*


Finished in 0.01999 seconds
1 example, 0 failures, 1 pending

Pending:
  User add some examples to (or delete)
  /Users/mhartl/rails_projects/sample_app/spec/models/user_spec.rb
  (Not Yet Implemented)

We’ll follow the advice of the default spec by filling it in with some RSpec examples, shown in Listing 6.10.

Listing 6.10. The initial user spec.
spec/models/user_spec.rb
require 'spec_helper'

describe User do

  before(:each) do
    @attr = { :name => "Example User", :email => "user@example.com" }
  end

  it "should create a new instance given valid attributes" do
    User.create!(@attr)
  end

  it "should require a name"
end

We’ve seen require and describe before, most recently in Listing 5.28. The next line is a before(:each) block; this was covered briefly in an exercise (Listing 3.33), and all it does is run the code inside the block before each example—in this case setting the @attr instance variable to an initialization hash.

The first example is just a sanity check, verifying that the User model is basically working. It uses User.create! (read “create bang”), which works just like the create method we saw in Section 6.1.3 except that it raises an ActiveRecord::RecordInvalid exception if the creation fails (similar to the ActiveRecord::RecordNotFound exception we saw in Section 6.1.4). As long as the attributes are valid, it won’t raise any exceptions, and the test will pass.

The final line is the test for the presence of the name attribute—or rather, it would be the actual test, if it had anything in it. Instead, the test is just a stub, but a useful stub it is: it’s a pending spec, which is a way to write a description of the application’s behavior without worrying yet about the implementation. Listing 6.9 shows an example of a pending spec using an explicit call to the pending method; in this case, since we have included only the it part of the example,

it "should require a name"

RSpec infers the existence of a pending spec.

Pending specs are handled well by programs for running specs, as seen for Autotest in Figure 6.5, and the output of rspec spec/ is similarly useful. Pending specs are useful as placeholders for tests we know we need to write at some point but don’t want to deal with right now.

autotest_pending_spec
Figure 6.5: Autotest (via autotest) with a pending User spec. (full size)

In order to fill in the pending spec, we need a way to make an attributes hash with an invalid name. (The @attr hash is valid by construction, with a non-blank name attribute.) The Hash method merge does the trick, as we can see with rails console:

>> @attr = { :name => "Example User", :email => "user@example.com" }
=> {:name => "Example User", :email => "user@example.com"}
>> @attr.merge(:name => "")
=> {:name => "", :email => "user@example.com"}

With merge in hand, we’re ready to make the new spec (using a trick I’ll explain momentarily), as seen in Listing 6.11.

Listing 6.11. A failing test for validation of the name attribute.
spec/models/user_spec.rb
describe User do

  before(:each) do
    @attr = { :name => "Example User", :email => "user@example.com" }
  end
  .
  .
  .
  it "should require a name" do
    no_name_user = User.new(@attr.merge(:name => ""))
    no_name_user.should_not be_valid
  end
end

Here we use merge to make a new user called no_name_user with a blank name. The second line then uses the RSpec should_not method to verify that the resulting user is not valid. The trick I alluded to above is related to be_valid: we know from earlier in this section that a User object responds to the valid? boolean method. RSpec adopts the useful convention of allowing us to test any boolean method by dropping the question mark and prepending be_. In other words,

no_name_user.should_not be_valid

is equivalent to

no_name_user.valid?.should_not == true

Since it sounds more like natural language, writing should_not be_valid is definitely more idiomatically correct RSpec.

With that, our new test should fail, which we can verify with Autotest or by running the user_spec.rb file using the spec script:

$ rspec spec/models/user_spec.rb
.F

1)
'User should require a name' FAILED
expected valid? to return false, got true
./spec/models/user_spec.rb:14:

2 examples, 1 failure

Now uncomment the validation (i.e., revert Listing 6.8 back to Listing 6.7) to get the test to pass:

$ rspec spec/models/user_spec.rb
..

2 examples, 0 failures

Of course, we also want to validate the presence of email addresses. The test (Listing 6.12) is analogous to the one for the name attribute.

Listing 6.12. A test for presence of the email attribute.
spec/models/user_spec.rb
describe User do

  before(:each) do
    @attr = { :name => "Example User", :email => "user@example.com" }
  end
  .
  .
  .
  it "should require an email address" do
    no_email_user = User.new(@attr.merge(:email => ""))
    no_email_user.should_not be_valid
  end
end

The implementation is also virtually the same, as seen in Listing 6.13.

Listing 6.13. Validating the presence of the name and email attributes.
app/models/user.rb
class User < ActiveRecord::Base 
  attr_accessible :name, :email

  validates :name,  :presence => true
  validates :email, :presence => true
end

Now all the tests should pass, and the “presence” validations are complete.

6.2.2 Length validation

We’ve constrained our User model to require a name for each user, but we should go further: the user’s names will be displayed on the sample site, so we should enforce some limit on their length. With all the work we did in Section 6.2.1, this step is easy.

We start with a test. There’s no science to picking a maximum length; we’ll just pull 50 out of thin air as a reasonable upper bound, which means verifying that names of 51 characters are too long (Listing 6.14).

Listing 6.14. A test for name length validation.
spec/models/user_spec.rb
describe User do

  before(:each) do
    @attr = { :name => "Example User", :email => "user@example.com" }
  end
  .
  .
  .
  it "should reject names that are too long" do
    long_name = "a" * 51
    long_name_user = User.new(@attr.merge(:name => long_name))
    long_name_user.should_not be_valid
  end
end

For convenience, we’ve used “string multiplication” in Listing 6.14 to make a string 51 characters long. We can see how this works using the console:

>> s = "a" * 51
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
>> s.length
=> 51

The test in Listing 6.14 should fail. To get it to pass, we need to know about the validation argument to constrain length, :length, along with the :maximum parameter to enforce the upper bound (Listing 6.15).

Listing 6.15. Adding a length validation for the name attribute.
app/models/user.rb
class User < ActiveRecord::Base 
  attr_accessible :name, :email

  validates :name,  :presence => true,
                    :length   => { :maximum => 50 }
  validates :email, :presence => true
end

With our test suite passing again, we can move on to a more challenging validation: email format.

6.2.3 Format validation

Our validations for the name attribute enforce only minimal constraints—any non-blank name under 51 characters will do—but of course the email attribute must satisfy more stringent requirements. So far we’ve only rejected blank email addresses; in this section, we’ll require email addresses to conform to the familiar pattern user@example.com.

Neither the tests nor the validation will be exhaustive, just good enough to accept most valid email addresses and reject most invalid ones. We’ll start with a couple tests involving collections of valid and invalid addresses. To make these collections, it’s worth knowing about a useful method for making arrays of strings, as seen in this console session:

>> %w[foo bar baz]
=> ["foo", "bar", "baz"]
>> addresses = %w[user@foo.com THE_USER@foo.bar.org first.last@foo.jp]
=> ["user@foo.com", "THE_USER@foo.bar.org", "first.last@foo.jp"]
>> addresses.each do |address|
?>   puts address
>> end
user@foo.com
THE_USER@foo.bar.org
first.last@foo.jp

Here we’ve iterated over the elements of the addresses array using the each method (Section 4.3.2). With this technique in hand, we’re ready to write some basic email format validation tests (Listing 6.16).

Listing 6.16. Tests for email format validation.
spec/models/user_spec.rb
describe User do

  before(:each) do
    @attr = { :name => "Example User", :email => "user@example.com" }
  end
  .
  .
  .
  it "should accept valid email addresses" do
    addresses = %w[user@foo.com THE_USER@foo.bar.org first.last@foo.jp]
    addresses.each do |address|
      valid_email_user = User.new(@attr.merge(:email => address))
      valid_email_user.should be_valid
    end
  end

  it "should reject invalid email addresses" do
    addresses = %w[user@foo,com user_at_foo.org example.user@foo.]
    addresses.each do |address|
      invalid_email_user = User.new(@attr.merge(:email => address))
      invalid_email_user.should_not be_valid
    end
  end
end

As noted above, these are far from exhaustive, but we do check the common valid email forms user@foo.com, THE_USER@foo.bar.org (uppercase, underscores, and compound domains), and first.last@foo.jp (the standard corporate username first.last, with a two-letter top-level domain jp), along with several invalid forms.

The application code for email format validation uses a regular expression (or regex) to define the format, along with the :format argument to the validates method (Listing 6.17).

Listing 6.17. Validating the email format with a regular expression.
app/models/user.rb
class User < ActiveRecord::Base
  attr_accessible :name, :email

  email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i

  validates :name,  :presence => true,
                    :length   => { :maximum => 50 }
  validates :email, :presence => true,
                    :format   => { :with => email_regex }
end

Here email_regex is a regular expression, also known as a regex. The code

  email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  .
  .
  .
  validates :email, :presence => true,
                    :format   => { :with => email_regex }

ensures that only email addresses that match the pattern will be considered valid.

So, where does the pattern come from? Regular expressions consist of a terse (some would say unreadable) language for matching text patterns; learning to construct regexes is an art, and to get you started I’ve broken email_regex into bite-sized pieces (Table 6.1).16 To really learn about regular expressions, though, I consider the amazing Rubular regular expression editor (Figure 6.6) to be simply essential.17 The Rubular website has a beautiful interactive interface for making regular expressions, along with a handy regex quick reference. I encourage you to study Table 6.1 with a browser window open to Rubular—no amount of reading about regular expressions can replace a couple of hours playing with Rubular.

ExpressionMeaning
/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/ifull regex
/start of regex
\Amatch start of a string
[\w+\-.]+at least one word character, plus, hyphen, or dot
@literal “at sign”
[a-z\d\-.]+at least one letter, digit, hyphen, or dot
\.literal dot
[a-z]+at least one letter
\zmatch end of a string
/end of regex
icase insensitive
Table 6.1: Breaking down the email regex from Listing 6.17.

By the way, there actually exists a full regex for matching email addresses according to the official standard, but it’s really not worth the trouble. The one in Listing 6.17 is fine, maybe even better than the official one.18

rubular
Figure 6.6: The awesome Rubular regular expression editor. (full size)

The tests should all be passing now. (In fact, the tests for valid email addresses should have been passing all along; since regexes are notoriously error-prone, the valid email tests are there mainly as a sanity check on email_regex.) This means that there’s only one constraint left: enforcing the email addresses to be unique.

6.2.4 Uniqueness validation

To enforce uniqueness of email addresses (so that we can use them as usernames), we’ll be using the :unique option to the validates method. But be warned: there’s a major caveat, so don’t just skim this section—read it carefully.

We’ll start, as usual, with our tests. In our previous model tests, we’ve mainly used User.new, which just creates a Ruby object in memory, but for uniqueness tests we actually need to put a record into the database.19 The (first) duplicate email test appears in Listing 6.18.

Listing 6.18. A test for the rejection of duplicate email addresses.
spec/models/user_spec.rb
describe User do

  before(:each) do
    @attr = { :name => "Example User", :email => "user@example.com" }
  end
  .
  .
  .
  it "should reject duplicate email addresses" do
    # Put a user with given email address into the database.
    User.create!(@attr)
    user_with_duplicate_email = User.new(@attr)
    user_with_duplicate_email.should_not be_valid
  end
end

The method here is to create a user and then try to make another one with the same email address. (We use the noisy method create!, first seen in Listing 6.10, so that it will raise an exception if anything goes wrong. Using create, without the bang !, risks having a silent error in our test, a potential source of elusive bugs.) We can get this test to pass with the code in Listing 6.19.20

Listing 6.19. Validating the uniqueness of email addresses.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  validates :email, :presence   => true,
                    :format     => { :with => email_regex },
                    :uniqueness => true
end

We’re not quite done, though. Email addresses are case-insensitive—foo@bar.com goes to the same place as FOO@BAR.COM or FoO@BAr.coM—so our validation should cover this case as well. We test for this with the code in Listing 6.20.

Listing 6.20. A test for the rejection of duplicate email addresses, insensitive to case.
spec/models/user_spec.rb
describe User do

  before(:each) do
    @attr = { :name => "Example User", :email => "user@example.com" }
  end
  .
  .
  .
  it "should reject email addresses identical up to case" do
    upcased_email = @attr[:email].upcase
    User.create!(@attr.merge(:email => upcased_email))
    user_with_duplicate_email = User.new(@attr)
    user_with_duplicate_email.should_not be_valid
  end
end

Here we are using the upcase method on strings (seen briefly in Section 4.3.2). This test does the same thing as the first duplicate email test, but with an upper-case email address instead. If this test feels a little abstract, go ahead and fire up the console:

$ rails console --sandbox
>> @attr = { :name => "Example User", :email => "user@example.com" }
=> {:name => "Example User", :email => "user@example.com"}
>> upcased_email = @attr[:email].upcase
=> "USER@EXAMPLE.COM"
>> User.create!(@attr.merge(:email => upcased_email))
>> user_with_duplicate_email = User.new(@attr)
>> user_with_duplicate_email.valid?
=> true

Of course, currently user_with_duplicate_email.valid? is true, since this is a failing test, but we want it to be false. Fortunately, :uniqueness accepts an option, :case_sensitive, for just this purpose (Listing 6.21).

Listing 6.21. Validating the uniqueness of email addresses, ignoring case.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  validates :email, :presence   => true,
                    :format     => { :with => email_regex },
                    :uniqueness => { :case_sensitive => false }
end

Note that we have simply replaced true with :case_sensitive => false; Rails infers in this case that :uniqueness should be true. At this point, our application (sort-of) enforces email uniqueness, and our test suite should pass.

The uniqueness caveat

There’s just one small problem, the caveat alluded to above:

Using validates :uniqueness does not guarantee uniqueness.

D’oh! But what can go wrong? Here’s what:

  1. Alice signs up for the sample app, with address alice@wonderland.com.
  2. Alice accidentally clicks on “Submit” twice, sending two requests in quick succession.
  3. The following sequence occurs: request 1 creates a user in memory that passes validation, request 2 does the same, request 1’s user gets saved, request 2’s user gets saved.
  4. Result: two user records with the exact same email address, despite the uniqueness validation.

If the above sequence seems implausible, believe me, it isn’t: it happens on any Rails website with significant traffic.21 Luckily, the solution is straightforward to implement; we just need to enforce uniqueness at the database level as well. Our method is to create a database index on the email column, and then require that the index be unique.

The email index represents an update to our data modeling requirements, which (as discussed in Section 6.1.1) is handled in Rails using migrations. We saw in Section 6.1.1 that generating the User model automatically created a new migration (Listing 6.2); in the present case, we are adding structure to an existing model, so we need to create a migration directly using the migration generator:

$ rails generate migration add_email_uniqueness_index

Unlike the migration for users, the email uniqueness migration is not pre-defined, so we need to fill in its contents with Listing 6.22.22

Listing 6.22. The migration for enforcing email uniqueness.
db/migrate/<timestamp>_add_email_uniqueness_index.rb
class AddEmailUniquenessIndex < ActiveRecord::Migration
  def self.up
    add_index :users, :email, :unique => true
  end

  def self.down
    remove_index :users, :email
  end
end

This uses a Rails method called add_index to add an index on the email column of the users table. The index by itself doesn’t enforce uniqueness, but the option :unique => true does.

The final step is to migrate the database:

$ rake db:migrate

Now the Alice scenario above will work fine: the database will save a user record based on the first request, and will reject the second save for violating the uniqueness constraint. (An error will appear in the Rails log, but that doesn’t do any harm. You can actually catch the ActiveRecord::StatementInvalid exception that gets raised—see Insoshi for an example—but in this tutorial we won’t bother with this step.) Adding this index on the email attribute accomplishes a second goal, alluded to briefly in Section 6.1.4: it fixes an efficiency problem in find_by_email (Box 6.2).

6.3 Viewing users

We’re not quite done with the basic user model—we still need to add passwords, a task for Chapter 7—but we do have enough in place to make a minimalist page for showing user information. This will allow a gentle introduction to the REST style of organizing the actions for our site’s users. Since this is just a rough demonstration for now, there are no tests in this section; we’ll add tests when we flesh out the user view in Section 7.3.

6.3.1 Debug and Rails environments

As preparation for adding dynamic pages to our sample application, now is a good time to add some debug information to our site layout (Listing 6.23). This displays some useful information about each page using the built-in debug method and params variable (which we’ll learn more about in Section 6.3.2), as seen in Figure 6.7.

Listing 6.23. Adding some debug information to the site layout.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  .
  .
  .
  <body>
    <div class="container">
      .
      .
      .
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div>
  </body>
</html>

Since we don’t want to display debug information to users of a deployed application, we use

if Rails.env.development?

to restrict the debug information to the development environment. Though we’ve seen evidence of Rails environments before (most recently in Section 6.1.3), this is the first time it has mattered to us.

home_page_with_debug_rails_3
Figure 6.7: The sample application Home page (/) with debug information at the bottom. (full size)

Rails comes equipped with three environments: test, development, and production.23 The default environment for the Rails console is development:

$ rails console 
Loading development environment (Rails 3.0.0)
>> Rails.env
=> "development"
>> Rails.env.development?
=> true
>> Rails.env.test?
=> false

As you can see, Rails provides a Rails object with an env attribute and associated environment boolean methods. In particular, Rails.env.development? is true only in a development environment, so the Embedded Ruby

<%= debug(params) if Rails.env.development? %>

won’t be inserted into production applications or tests. (Inserting the debug information into tests probably doesn’t do any harm, but it probably doesn’t do any good, either, so it’s best to restrict the debug display to development only.)

If you ever need to run a console in a different environment (to debug a test, for example), you can pass the environment as a parameter to the console script:

$ rails console test
Loading test environment (Rails 3.0.0)
>> Rails.env
=> "test"

As with the console, development is the default environment for the local Rails server, but you can also run it in a different environment:

$ rails server --environment production

If you view your app running in production, it won’t work without a production database, which we can create by running rake db:migrate in production:

$ rake db:migrate RAILS_ENV=production

(I find it confusing that the console, server, and migrate commands specify non-default environments in three mutually incompatible ways, which is why I bothered showing all three.)

By the way, if you have deployed your sample app to Heroku, you can see its environment using the heroku command, which provides its own (remote) console:

$ heroku console
Ruby console for yourapp.heroku.com
>> Rails.env
=> "production"
>> Rails.env.production?
=> true

Naturally, since Heroku is a platform for production sites, it runs each application in a production environment.

6.3.2 User model, view, controller

In order to make a page to view a user, we’ll use the User model to put a user into the database, make a view to display some user information, and then add an action to the Users controller to handle the browser request. In other words, for the first time in this tutorial, we’ll see in one place all three elements of the model-view-controller architecture first discussed in Section 1.2.6.

Our first step is to create a user using the console, which we’ll take care not to start in a sandbox since this time the whole point is to save a record to the database:

$ rails console
Loading development environment (Rails 3.0.0)
>> User.create!(:name => "Michael Hartl", :email => "mhartl@example.com")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2010-01-07 23:05:14", updated_at: "2010-01-07 23:05:14">

To double-check that this worked, let’s look at the row in the development database using the SQLite Database Browser (Figure 6.8). Note that the columns correspond to the attributes of the data model defined in Section 6.1.

sqlite_user_row
Figure 6.8: A user row in the SQLite database db/development.sqlite3(full size)

Next comes the view, which is minimalist to emphasize that this is just a demonstration (Listing 6.24). We use the standard Rails location for showing a user, app/views/users/show.html.erb; unlike the new.html.erb view, which we created with the generator in Listing 5.23, the show.html.erb file doesn’t currently exist, so you’ll have to create it by hand.

Listing 6.24. A stub view for showing user information.
app/views/users/show.html.erb
<%= @user.name %>, <%= @user.email %>

This view uses Embedded Ruby to display the user’s name and email address, assuming the existence of an instance variable called @user. Of course, eventually the real user show page will look very different, and won’t display the email address publicly.

Finally, we’ll add the show action to the Users controller (corresponding to the show.html.erb view) with the code in Listing 6.25, which defines the @user instance variable needed by the view.

Listing 6.25. The Users controller with a show action.
app/controllers/users_controller.rb
class UsersController < ApplicationController

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

  def new
    @title = "Sign up"
  end
end

Here we’ve gotten a little ahead of ourselves by using the standard Rails params object to retrieve the user id. When we make the appropriate request to the Users controller, params[:id] will be the user id 1, so the effect is the same as the find command

User.find(1)

we saw in Section 6.1.4.

Although the show view and action are now both defined, we still don’t have a way to view the page itself. The requires defining the proper rule in the Rails routes file, as we’ll see in the next section.

6.3.3 A Users resource

Our method for displaying the user show page will follow the conventions of the REST architecture favored in Rails applications. This style is based on the ideas of representational state transfer identified and named by computer scientist Roy Fielding in his doctoral dissertation Architectural Styles and the Design of Network-based Software Architectures.24 The REST design style emphasizes representing data as resources that can be created, shown, updated, or destroyed—four actions corresponding to the four fundamental operations POST, GET, PUT, and DELETE defined by the HTTP standard (Box 3.1).

When following REST principles, resources are typically referenced using the resource name and a unique identifier. What this means in the context of users—which we’re now thinking of as a Users resource—is that we should view the user with id 1 by issuing a GET request to the URL /users/1. Here the show action is implicit in the type of request—when Rails’ REST features are activated, GET requests are automatically handled by the show action.

user_show_exception_caught
Figure 6.9: The initial effect of hitting /users/1(full size)

Unfortunately, the URL /users/1 doesn’t work quite yet due to a routing error (Figure 6.9). We can get the REST-style Users URL to work by adding users as a resource to config/routes.rb, as seen in Listing 6.26.

Listing 6.26. Adding a Users resource to the routes file.
config/routes.rb
SampleApp::Application.routes.draw do
  resources :users

  match '/signup',  :to => 'users#new'
  .
  .
  .
end

After adding the routes for the Users resource, the URL /users/1 works perfectly (Figure 6.10).

user_show_rails_3
Figure 6.10: The user show page at /users/1 after adding a Users resource. (full size)

You might have noticed that Listing 6.26 removed the line

get "users/new"

last seen in Listing 5.29. This is because the one additional resource line in Listing 6.26 doesn’t just add a working /users/1 URL; it endows our sample application with all the actions needed for a RESTful Users resource,25 along with a large number of named routes (Section 5.2.3) for generating user URLs. The resulting correspondence of URLs, actions, and named routes is shown in Table 6.2. (Compare to Table 2.2.) Over the course of the next three chapters, we’ll cover all of the other entries in Table 6.2 as we fill in all the actions necessary to make users a fully RESTful resource.

HTTP requestURLActionNamed routePurpose
GET/usersindexusers_pathpage to list all users
GET/users/1showuser_path(1)page to show user with id 1
GET/users/newnewnew_user_pathpage to make a new user (signup)
POST/userscreateusers_pathcreate a new user
GET/users/1/editeditedit_user_path(1)page to edit user with id 1
PUT/users/1updateuser_path(1)update user with id 1
DELETE/users/1destroyuser_path(1)delete user with id 1
Table 6.2: RESTful routes provided by the Users resource in Listing 6.26.

params in debug

Before leaving the user show page, we’ll take a moment to examine the debug information produced by Listing 6.23. If you look closely at Figure 6.10, you’ll see that it includes useful information about the page being rendered:26

--- !map:ActiveSupport::HashWithIndifferentAccess
action: show
id: "1"
controller: users

This is a YAML27 representation of params, which (as hinted at by the name HashWithIndifferentAccess) is basically a hash. We see that its controller is users, its action is show, and its id attribute is "1". Although you will rarely have occasion to use params[:controller] or params[:action], using params[:id] to pull out the id from the URL is a common Rails idiom. In particular, we used the code

User.find(params[:id])

in Listing 6.25 to find the user with id 1. (The find method knows how to convert the string "1" into the integer 1.)

The debug information often provides useful feedback when developing Rails application, and I suggest getting in the habit of checking it whenever you application doesn’t behave as expected.

6.4 Conclusion

This chapter is the first half of the two-step process of creating a working User model. Our users now have name and email attributes, together with validations enforcing several important constraints on their values. We’ve also taken a first small step toward a working user show page and a Users resource based on the principles of representational state transfer (REST). In Chapter 7, we’ll complete the process by adding user passwords and a more useful user view.

If you’re using Git, now would be a good time to commit if you haven’t done so in a while:

$ git add .
$ git commit -am "Finished first cut of the User model"

6.5 Exercises

  1. Read through the Rails API entry for ActiveRecord::Base to get a sense of its capabilities.
  2. Study the entry in the Rails API for the validates method to learn more about its capabilities and options.
  3. Spend a couple hours playing with Rubular.
  1. Mockingbird doesn’t support custom images like the profile photo in Figure 6.1; I put that in by hand using Adobe Fireworks. The hippo here is from http://www.flickr.com/photos/43803060@N00/24308857/
  2. The name comes from the “active record pattern”, identified and named in Patterns of Enterprise Application Architecture by Martin Fowler. 
  3. Pronounced “ess-cue-ell”, though the alternate pronunciation “sequel” is also common. 
  4. In its earliest incarnations, Rails did require knowledge of an SQL DDL. Even after Rails added migrations, setting up the old default database (MySQL) was quite involved. Happily, as noted in Section 1.2.5, Rails now uses SQLite by default, which stores its data as a simple file—no setup required. 
  5. Occasionally, it is necessary to pierce this abstraction layer, but one design goal of this tutorial is to make all the code database-independent. (Indeed, this is a worthy goal in general.) In case you ever do need to write database-specific code to deploy on Heroku, you should know that they use the excellent PostgreSQL (“post-gres-cue-ell”) database. PostgreSQL is free, open-source, and cross-platform; if you develop PostgreSQL-specific applications, you can install it locally, and configure Rails to use it in development by editing the config/database.yml file. Such configuration is beyond the scope of this tutorial, but there are lots of resources on the web; use a search engine to find the most up-to-date information for your platform. 
  6. By using an email address as the username, we open the theoretical possibility of communicating with our users at a future date. 
  7. Don’t worry about exactly how the t object manages to do this; the beauty of abstraction layers is that we don’t have to know. We can just trust the t object to do its job. 
  8. We’ll see how to migrate up on a remote Heroku server in Section 7.4.2
  9. Officially pronounced “ess-cue-ell-ite”, although the (mis)pronunciation “sequel-ite” is also common. 
  10. In case you’re curious about "2010-01-05 00:57:46", I’m not writing this after midnight; the timestamps are recorded in Coordinated Universal Time (UTC), which for most practical purposes is the same as Greenwich Mean Time. From the NIST Time and Frequency FAQ: Q: Why is UTC used as the acronym for Coordinated Universal Time instead of CUT? A: In 1970 the Coordinated Universal Time system was devised by an international advisory group of technical experts within the International Telecommunication Union (ITU). The ITU felt it was best to designate a single abbreviation for use in all languages in order to minimize confusion. Since unanimous agreement could not be achieved on using either the English word order, CUT, or the French word order, TUC, the acronym UTC was chosen as a compromise. 
  11. Note the value of user.updated_at. Told you the timestamp was in UTC. 
  12. Exceptions and exception handling are somewhat advanced Ruby subjects, and we won’t need them much in this book. They are important, though, and I suggest learning about them using one of the Ruby books recommended in Section 1.1.1
  13. To those worried that find_by_email will be inefficient if there are a large number of users, you’re ahead of the game. We’ll cover this issue, and its solution via database indices, in Section 6.2.4
  14. and written 
  15. I’ll omit the output of console commands when they are not particularly instructive—for example, the results of User.new
  16. Note that, in Table 6.1, “letter” really means “lower-case letter”, but the i at the end of the regex enforces case-insensitive matching. 
  17. If you find it as useful as I do, I encourage you to donate to Rubular to reward developer Michael Lovitt for his wonderful work. 
  18. Did you know that "Michael Hartl"@example.com, with quotation marks and a space in the middle, is a valid email address according to the standard? Incredibly, it is—but it’s absurd. If you don’t have an email address that contains only letters, numbers, underscores, and dots, then get one. N.B. The regex in Listing 6.17 allows plus signs, too, because Gmail (and possibly other email services) does something useful with them: for example, to filter orders from Amazon, you can use username+amazon@gmail.com, which will go to the Gmail address username@gmail.com, allowing you to filter on the string amazon
  19. As noted briefly in the introduction to this section, there is a dedicated test database, db/test.sqlite3, for this purpose. 
  20. If you’re wondering why the create! line in Listing 6.10 doesn’t cause this to fail by creating a duplicate user, it’s because Rails tests are transactional: each test is wrapped in a transaction, which rolls back the database after the test executes. This way, each test runs against a fresh database. 
  21. Yes, it happened to me. How do you think I found out about this issue? 
  22. Of course, we could just edit the migration file for the users table in Listing 6.2 but that would require rolling back and then migrating back up. The Rails Way is to use migrations every time we discover that our data model needs to change. 
  23. You can define your own custom environments as well; see the Railscast on adding an environment for details. 
  24. Fielding, Roy Thomas. Architectural Styles and the Design of Network-based Software Architectures. Doctoral dissertation, University of California, Irvine, 2000. 
  25. This means that the routing works, but the corresponding pages don’t necessarily work at this point. For example, /users/1/edit gets routed properly to the edit action of the Users controller, but since the edit action doesn’t exist yet actually hitting that URL will return an error. 
  26. Some of this tutorial’s screenshots show debug information with output like !map:HashWithIndifferentAccess instead of !map:ActiveSupport::HashWithIndifferentAccess. This is simply a minor difference between Rails 2.3 and Rails 3. Since the rendered web pages are otherwise identical between Rails versions, this one footnote saves me the trouble of redoing all the screenshots. 
  27. The Rails debug information is shown as YAML (a recursive acronym standing for “YAML Ain’t Markup Language”), which is a friendly data format designed to be both machine- and human-readable.