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 12 Following users

In this chapter, we will complete the core sample application by adding a social layer that allows users to follow (and unfollow) other users, resulting in each user’s Home page displaying a status feed of the followed users’ microposts. We will also make views to display both a user’s followers and the users each user is following. We will learn how to model user following in Section 12.1, and then make the web interface in Section 12.2 (including an introduction to Ajax). Finally, we’ll end by developing a fully functional status feed in Section 12.3.

This final chapter contains some of the most challenging material in the tutorial, including a complicated data model and some Ruby/SQL trickery to make the status feed. Through these examples, you will see how Rails can handle even rather intricate data models, which should serve you well as you go on to develop your own applications with their own specific requirements. To help with the transition from tutorial to independent development, Section 12.4 contains suggested extensions to the core sample application, along with pointers to more advanced resources.

As usual, Git users should create a new topic branch:

$ git checkout -b following-users

Because the material in this chapter is particularly challenging, before writing any code we’ll pause for a moment and take a tour of user following. As in previous chapters, at this early stage we’ll represent pages using mockups.1 The full page flow runs as follows: a user (John Calvin) starts at his profile page (Figure 12.1) and navigates to the Users page (Figure 12.2) to select a user to follow. Calvin navigates to the profile of a second user, Thomas Hobbes (Figure 12.3), clicking on the “Follow” button to follow that user. This changes the “Follow” button to “Unfollow”, and increments Hobbes’s “followers” count by one (Figure 12.4). Navigating to his home page, Calvin now sees an incremented “following” count and finds Hobbes’s microposts in his status feed (Figure 12.5). The rest of this chapter is dedicated to making this page flow actually work.

page_flow_profile_mockup
Figure 12.1: A mockup of the current user’s profile. (full size)
page_flow_user_index_mockup
Figure 12.2: A mockup of finding a user to follow. (full size)
page_flow_other_profile_follow_button_mockup
Figure 12.3: A mockup of the profile of another user, with a follow button. (full size)
page_flow_other_profile_unfollow_button_mockup
Figure 12.4: A profile mockup with an unfollow button and incremented followers count. (full size)
page_flow_home_page_feed_mockup
Figure 12.5: A Home page mockup, with status feed and incremented following count. (full size)

12.1 The Relationship model

Our first step in implementing user following and followers is to construct a data model, which is not as straightforward as it seems. Naïvely, it seems that a has_many relationship should do: a user has_many following and has_many followers. As we will see, there is a problem with this approach, and we’ll learn how to fix it using has_many :through. It’s likely that many of the ideas in this section won’t seem obvious at first, and it may take a while for the rather complicated data model to sink in. If you find yourself getting confused, try pushing forward to the end; then, read the section a second time through to see if things are clearer.

12.1.1 A problem with the data model (and a solution)

As a first step toward constructing a data model for user following, let’s examine a typical case. For instance, consider a user who follows a second user: we could say that, e.g., Calvin is following Hobbes, and Hobbes is followed by Calvin, so that Calvin is the follower and Hobbes is followed. Using Rails’ default pluralization convention, the set of all such followed users would be called the followeds, but that is ungrammatical and clumsy; instead, we will override the default and call them following, so that user.following will contain an array of the users being followed. Similarly, the set of all users following a given user is that user’s followers, and user.followers will be an array of those users.

This suggests modeling the following users as in Figure 12.6, with a following table and a has_many association. Since user.following should be an array of users, each row of the following table would need to be a user, as identified by the followed_id, together with the follower_id to establish the association.2 In addition, since each row is a user, we would need to include the user’s other attributes, including the name, password, etc.

naive_user_has_many_following
Figure 12.6: A naïve implementation of user following.

The problem with the data model in Figure 12.6 is that it is terribly redundant: each row contains not only each followed user’s id, but all their other information as well—all of which is already in the users table. Even worse, to model user followers we would need a separate followers table. Finally, this data model is a maintainability nightmare, since each time a user changed (say) his name, we would need to update not just the user’s record in the users table but also every row containing that user in both the following and followers tables.

The problem here is that we are missing an underlying abstraction. One way to find the proper abstraction is to consider how we might implement following in a web application. Recall from Section 6.3.3 that the REST architecture involves resources that are created and destroyed. This leads us to ask two questions: When a user follows another user, what is being created? When a user unfollows another user, what is being destroyed?

Upon reflection, we see that in these cases the application should either create or destroy a relationship (or connection3) between two users. A user then has_many :relationships, and has many following (or followers) through those relationships. Indeed, Figure 12.6 already contains most of the implementation: since each followed user is uniquely identified by followed_id, we could convert following to a relationships table, omit the user details, and use followed_id to retrieve the followed user from the users table. Moreover, by considering reverse relationships, we could use the follower_id column to extract an array of user’s followers.

To make a following array of users, it would be possible to pull out an array of followed_id attributes and then find the user for each one. As you might expect, though, Rails has a way to make this procedure more convenient; the relevant technique is known as has_many :through.4 As we will see in Section 12.1.4, Rails allows us to say that a user is following many users through the relationships table, using the succinct code

has_many :following, :through => :relationships, :source => "followed_id"

This code automatically populates user.following with an array of followed users. A diagram of the data model appears in Figure 12.7.

user_has_many_following
Figure 12.7: A model of user following through an intermediate Relationship model. (full size)

To get started with the implementation, we first generate a Relationship model as follows:

$ rails generate model Relationship follower_id:integer followed_id:integer

Since we will be finding relationships by follower_id and by followed_id, we should add an index on each column for efficiency, as shown in Listing 12.1.

Listing 12.1. Adding indices for the relationships table.
db/migrate/<timestamp>_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration
  def self.up
    create_table :relationships do |t|
      t.integer :follower_id
      t.integer :followed_id

      t.timestamps
    end
    add_index :relationships, :follower_id
    add_index :relationships, :followed_id
    add_index :relationships, [:follower_id, :followed_id], :unique => true
  end

  def self.down
    drop_table :relationships
  end
end

Listing 12.1 also includes a composite index that enforces uniqueness of pairs of (follower_id, followed_id), so that a user can’t follow another user more than once:

add_index :relationships, [:follower_id, :followed_id], :unique => true

(Compare to the email uniqueness index from Listing 6.22.) As we’ll see starting in Section 12.1.4, our user interface won’t allow this to happen, but adding a unique index arranges to raise an error if a user tries to create duplicate relationships anyway (using, e.g., a command-line tool such as cURL). We could also add a uniqueness validation to the Relationship model, but because it is always an error to create duplicate relationships the unique index is sufficient for our purposes.

To create the relationships table, we migrate the database and prepare the test database as usual:

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

The result is the Relationship data model shown in Figure 12.8.

relationship_model
Figure 12.8: The Relationship data model.

As with any new model, before moving on, we should define the model’s accessible attributes. In the case of the Relationship model, the followed_id should be accessible, since users will create relationships through the web, but the follower_id attribute should not be accessible; otherwise, malicious users could force other users to follow them. The result appears in Listing 12.2.

Listing 12.2. Making a relationship’s followed_id (but not follower_id) accessible.
app/models/relationship.rb
class Relationship < ActiveRecord::Base
  attr_accessible :followed_id
end

12.1.2 User/relationship associations

Before implementing following and followers, we first need to establish the association between users and relationships. A user has_many relationships, and—since relationships involve two users—a relationship belongs_to both a follower and a followed user.

As with microposts in Section 11.1.2, we will create new relationships using the user association, with code such as

user.relationships.create(:followed_id => ...)

We start with a test, shown in Listing 12.3, which builds an @relationship instance variable (used below) and makes sure that it can be saved using save!. As with create!, the save! method raises an exception if the save fails; compare this to the use of create! in Listing 11.4.

Listing 12.3. Testing Relationship creation with save!.
spec/models/relationship_spec.rb
require 'spec_helper'

describe Relationship do

  before(:each) do
    @follower = Factory(:user)
    @followed = Factory(:user, :email => Factory.next(:email))

    @relationship = @follower.relationships.build(:followed_id => @followed.id)
  end

  it "should create a new instance given valid attributes" do
    @relationship.save!
  end
end

We should also test the User model for a relationships attribute, as shown in Listing 12.4.

Listing 12.4. Testing for the user.relationships attribute.
spec/models/user_spec.rb
describe User do
  .
  .
  .
  describe "relationships" do

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

    it "should have a relationships method" do
      @user.should respond_to(:relationships)
    end
  end
end

At this point you might expect application code as in Section 11.1.2, and it’s similar, but there is one critical difference: in the case of the Micropost model, we could say

class Micropost < ActiveRecord::Base
  belongs_to :user
  .
  .
  .
end

and

class User < ActiveRecord::Base
  has_many :microposts
  .
  .
  .
end

because the microposts table has a user_id attribute to identify the user (Section 11.1.1). An id used in this manner to connect two database tables is known as a foreign key, and when the foreign key for a User model object is user_id, Rails can infer the association automatically: by default, Rails expects a foreign key of the form <class>_id, where <class> is the lower-case version of the class name.5 In the present case, although we are still dealing with users, they are now identified with the foreign key follower_id, so we have to tell that to Rails, as shown in Listing 12.5.6

Listing 12.5. Implementing the user/relationships has_many association.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  has_many :microposts, :dependent => :destroy
  has_many :relationships, :foreign_key => "follower_id",
                           :dependent => :destroy
  .
  .
  .
end

(Since destroying a user should also destroy that user’s relationships, we’ve gone ahead and added :dependent => :destroy to the association; writing a test for this is left as an exercise (Section 12.5).) At this point, the association tests in Listing 12.3 and Listing 12.4 should pass.

As with the Micropost model, the Relationship model has a belongs_to relationship with users; in this case, a relationship object belongs to both a follower and a followed user, which we test for in Listing 12.6.

Listing 12.6. Testing the user/relationships belongs_to association.
spec/models/relationship_spec.rb
describe Relationship do
  .
  .
  .
  describe "follow methods" do

    before(:each) do
      @relationship.save
    end

    it "should have a follower attribute" do
      @relationship.should respond_to(:follower)
    end

    it "should have the right follower" do
      @relationship.follower.should == @follower
    end

    it "should have a followed attribute" do
      @relationship.should respond_to(:followed)
    end

    it "should have the right followed user" do
      @relationship.followed.should == @followed
    end
  end
end

To write the application code, we define the belongs_to relationship as usual. Rails infers the names of the foreign keys from the corresponding symbols (i.e., follower_id from :follower, and followed_id from :followed), but since there is neither a Followed nor a Follower model we need to supply the class name User. The result is shown in Listing 12.7.

Listing 12.7. Adding the belongs_to associations to the Relationship model.
app/models/relationship.rb
class Relationship < ActiveRecord::Base
  attr_accessible :followed_id

  belongs_to :follower, :class_name => "User"
  belongs_to :followed, :class_name => "User"
end

The followed association isn’t actually needed until Section 12.1.5, but the parallel follower/followed structure is clearer if we implement them both at the same time.

12.1.3 Validations

Before moving on, we’ll add a couple of Relationship model validations for completeness. The tests (Listing 12.8) and application code (Listing 12.9) are straightforward.

Listing 12.8. Testing the Relationship model validations.
spec/models/relationship_spec.rb
describe Relationship do
  .
  .
  .
  describe "validations" do

    it "should require a follower_id" do
      @relationship.follower_id = nil
      @relationship.should_not be_valid
    end

    it "should require a followed_id" do
      @relationship.followed_id = nil
      @relationship.should_not be_valid
    end
  end
end
Listing 12.9. Adding the Relationship model validations.
app/models/relationship.rb
class Relationship < ActiveRecord::Base
  attr_accessible :followed_id

  belongs_to :follower, :class_name => "User"
  belongs_to :followed, :class_name => "User"

  validates :follower_id, :presence => true
  validates :followed_id, :presence => true
end

12.1.4 Following

We come now to the heart of the Relationship associations: following and followers. We start with following, as shown Listing 12.10.

Listing 12.10. A test for the user.following attribute.
spec/models/user_spec.rb
describe User do
  .
  .
  .
  describe "relationships" do

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

    it "should have a relationships method" do
      @user.should respond_to(:relationships)
    end

    it "should have a following method" do
      @user.should respond_to(:following)
    end
  end
end

The implementation uses has_many :through for the first time: a user has many following through relationships, as illustrated in Figure 12.7. By default, in a has_many :through association Rails looks for a foreign key corresponding to the singular version of the association; in other words, code like

has_many :followeds, :through => :relationships

would assemble an array using the followed_id in the relationships table. But, as noted in Section 12.1.1, user.followeds is rather awkward; far more natural is to treat “following” as a plural of “followed”, and write instead user.following for the array of followed users. Naturally, Rails allows us to override the default, in this case using the :source parameter (Listing 12.11), which explicitly tells Rails that the source of the following array is the set of followed ids.

Listing 12.11. Adding the User model following association with has_many :through.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  has_many :microposts, :dependent => :destroy
  has_many :relationships, :foreign_key => "follower_id",
                           :dependent => :destroy
  has_many :following, :through => :relationships, :source => :followed
  .
  .
  .
end

To create a following relationship, we’ll introduce a follow! utility method so that we can write user.follow!(other_user).7 We’ll also add an associated following? boolean method to test if one user is following another.8 The tests in Listing 12.12 show how we expect these methods to be used in practice.

Listing 12.12. Tests for some “following” utility methods.
spec/models/user_spec.rb
describe User do
  .
  .
  .
  describe "relationships" do
    .
    .
    .
    it "should have a following? method" do
      @user.should respond_to(:following?)
    end

    it "should have a follow! method" do
      @user.should respond_to(:follow!)
    end

    it "should follow another user" do
      @user.follow!(@followed)
      @user.should be_following(@followed)
    end

    it "should include the followed user in the following array" do
      @user.follow!(@followed)
      @user.following.should include(@followed)
    end
  end
end

Note that we have replaced the include? method seen in Listing 11.31 with should include, effectively transforming

@user.following.include?(@followed).should be_true

into the clearer and more succinct

@user.following.should include(@followed)

This example shows just how flexible the RSpec boolean convention is; even though include is already a Ruby keyword (used to include a module, as seen in, e.g., Listing 9.11), in this context RSpec correctly guesses that we want to test array inclusion.

In the application code, the following? method takes in a user, called followed, and checks to see if a follower with that id exists in the database; the follow! method calls create! through the relationships association to create the following relationship. The results appear in Listing 12.13.9

Listing 12.13. The following? and follow! utility methods.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  def self.authenticate_with_salt(id, cookie_salt)
    .
    .
    .
  end

  def following?(followed)
    relationships.find_by_followed_id(followed)
  end

  def follow!(followed)
    relationships.create!(:followed_id => followed.id)
  end
  .
  .
  .
end

Note that in Listing 12.13 we have omitted the user itself, writing just

relationships.create!(...)

instead of the equivalent code

self.relationships.create!(...)

Whether to include the explicit self is largely a matter of taste.

Of course, users should be able to unfollow other users as well as follow them, which leads to the somewhat predictable unfollow! method, as shown in Listing 12.14.10

Listing 12.14. A test for unfollowing a user.
spec/models/user_spec.rb
describe User do
  .
  .
  .
  describe "relationships" do
    .
    .
    .
    it "should have an unfollow! method" do
      @followed.should respond_to(:unfollow!)
    end

    it "should unfollow a user" do
      @user.follow!(@followed)
      @user.unfollow!(@followed)
      @user.should_not be_following(@followed)
    end
  end
end

The code for unfollow! is straightforward: just find the relationship by followed id and destroy it (Listing 12.15).11

Listing 12.15. Unfollowing a user by destroying a user relationship.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  def following?(followed)
    relationships.find_by_followed_id(followed)
  end

  def follow!(followed)
    relationships.create!(:followed_id => followed.id)
  end

  def unfollow!(followed)
    relationships.find_by_followed_id(followed).destroy
  end
  .
  .
  .
end

12.1.5 Followers

The final piece of the relationships puzzle is to add a user.followers method to go with user.following. You may have noticed from Figure 12.7 that all the information needed to extract an array of followers is already present in the relationships table. Indeed, the technique is exactly the same as for user following, with the roles of follower_id and followed_id reversed. This suggests that, if we could somehow arrange for a reverse_relationships table with those two columns reversed (Figure 12.9), we could implement user.followers with little effort.

user_has_many_followers
Figure 12.9: A model for user followers using a reverse Relationship model. (full size)

We begin with the tests, having faith that the magic of Rails will come to the rescue (Listing 12.16).

Listing 12.16. Testing for reverse relationships.
spec/models/user_spec.rb
describe User do
  .
  .
  .
  describe "relationships" do
    .
    .
    .    
    it "should have a reverse_relationships method" do
      @user.should respond_to(:reverse_relationships)
    end

    it "should have a followers method" do
      @user.should respond_to(:followers)
    end

    it "should include the follower in the followers array" do
      @user.follow!(@followed)
      @followed.followers.should include(@user)
    end
  end
end

As you probably suspect, we will not be making a whole database table just to hold reverse relationships. Instead, we will exploit the underlying symmetry between followers and following to simulate a reverse_relationships table by passing followed_id as the primary key. In other words, where the relationships association uses the follower_id foreign key,

has_many :relationships, :foreign_key => "follower_id"

the reverse_relationships association uses followed_id:

has_many :reverse_relationships, :foreign_key => "followed_id"

The followers association then gets built through the reverse relationships, as shown in Listing 12.17.

Listing 12.17. Implementing user.followers using reverse relationships.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  has_many :reverse_relationships, :foreign_key => "followed_id",
                                   :class_name => "Relationship",
                                   :dependent => :destroy
  has_many :followers, :through => :reverse_relationships, :source => :follower
  .
  .
  .
end

(As with Listing 12.5, the test for dependent :destroy is left as an exercise (Section 12.5).) Note that we actually have to include the class name for this association, i.e.,

has_many :reverse_relationships, :foreign_key => "followed_id",
                                 :class_name => "Relationship"

because otherwise Rails will look for a ReverseRelationship class, which doesn’t exist.

It’s also worth noting that we could actually omit the :source key in this case, using simply

has_many :followers, :through => :reverse_relationships

since Rails will automatically look for the foreign key follower_id in this case. I’ve kept the :source key to emphasize the parallel structure with the has_many :following association, but you are free to leave it out.

With the code in Listing 12.17, the following/follower associations are complete, and all the tests should pass. This section has placed rather heavy demands on your data modeling skills, and it’s fine if it takes a while to soak in. In fact, one of the best ways to understand the associations is to use them in the web interface, as seen in the next section.

12.2 A web interface for following and followers

In the introduction to this chapter, we saw a preview of the page flow for user following. In this section, we will implement the basic interface and following/unfollowing functionality shown in those mockups. We will also make separate pages to show the user following and followers arrays. In Section 12.3, we’ll complete our sample application by adding the user’s status feed.

12.2.1 Sample following data

As in previous chapters, we will find it convenient to use the sample data Rake task to fill the database with sample relationships. This will allow us to design the look and feel of the web pages first, deferring the back-end functionality until later in this section.

When we last left the sample data populator in Listing 11.20, it was getting rather cluttered, so we begin by defining separate methods to make users and microposts, and then add sample relationship data using a new make_relationships method. The results are shown in Listing 12.18.

Listing 12.18. Adding following/follower relationships to the sample data.
lib/tasks/sample_data.rake
namespace :db do
  desc "Fill database with sample data"
  task :populate => :environment do
    Rake::Task['db:reset'].invoke
    make_users
    make_microposts
    make_relationships
  end
end

def make_users
  admin = User.create!(:name => "Example User",
                       :email => "example@railstutorial.org",
                       :password => "foobar",
                       :password_confirmation => "foobar")
  admin.toggle!(:admin)
  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

def make_microposts
  User.all(:limit => 6).each do |user|
    50.times do
      content = Faker::Lorem.sentence(5)
      user.microposts.create!(:content => content)
    end
  end
end

def make_relationships
  users = User.all
  user  = users.first
  following = users[1..50]
  followers = users[3..40]
  following.each { |followed| user.follow!(followed) }
  followers.each { |follower| follower.follow!(user) }
end

Here the sample relationships are created using the code

def make_relationships
  users = User.all
  user  = users.first
  following = users[1..50]
  followers = users[3..40]
  following.each { |followed| user.follow!(followed) }
  followers.each { |follower| follower.follow!(user) }
end

We somewhat arbitrarily arrange for the first user to follow the next 50 users, and then have users with ids 4 through 41 follow that user back. The resulting relationships will be sufficient for developing the application interface.

To execute the code in Listing 12.18, populate the database as usual:

$ bundle exec rake db:populate

12.2.2 Stats and a follow form

Now that our sample users have both following and followers arrays, we need to update the profile pages and home pages to reflect this. We’ll start by making a partial to display the following and follower statistics on the profile and home pages, as mocked up in Figure 12.1 and Figure 12.5. The result will be displays of the number following and the number of followers, together with links to their dedicated display pages. We’ll next add a follow/unfollow form, and then make dedicated pages for showing user following and followers.

stats_partial_mockup
Figure 12.10: A mockup of the stats partial.

A close-up of the stats area, taken from the mockup in Figure 12.1, appears in Figure 12.10. These stats consist of a count of the number of users the current user is following and that user’s number of followers, each of which should be a link to its respective dedicated display page. In Chapter 5, we stubbed out such links with the dummy text ’#’, but that was before we had much experience with routes. This time, although we’ll defer the actual pages to Section 12.2.3, we’ll make the routes now, as seen in Listing 12.19. This code uses the :member method inside a resources block, which we haven’t seen before, but see if you can guess what it does.

Listing 12.19. Adding following and followers actions to the Users controller.
config/routes.rb
SampleApp::Application.routes.draw do
  resources :users do
    member do
      get :following, :followers
    end
  end
  .
  .
  .
end

You might suspect that the URLs for user following and followers will look like /users/1/following and /users/1/followers, and that is exactly what the code in Listing 12.19 does. Since both pages will be showing data, we use get to arrange for the URLs to respond to GET requests (as required by the REST convention for such pages), and the member method means that the routes respond to URLs containing the user id. (The other possibility, collection, works without the id, so that

resources :users do
  collection do
    get :tigers
  end
end

would respond to the URL /users/tigers—presumably to display all the tigers in our application. For more details on such routing options, see the Rails Guides article on “Rails Routing from the Outside In”.) A table of the routes generated by Listing 12.19 appears in Table 12.1; note the named routes for the following and followers pages, which we’ll put to use momentarily.

HTTP requestURLActionNamed route
GET/users/1/followingfollowingfollowing_user_path(1)
GET/users/1/followersfollowersfollowers_user_path(1)
Table 12.1: RESTful routes provided by the custom rules in resource in Listing 12.19.

With the routes defined, we are now in a position to make tests for the stats partial. (We could have written the tests first, but the named routes would have been hard to motivate without the updated routes file.) We could write tests for the user profile page, since the stats partial will appear there, but it will also appear on the Home page, and this is a nice opportunity to refactor the Home page tests to take into account users signing in. The result appears in Listing 12.20.

Listing 12.20. Testing the following/follower statistics on the Home page.
spec/controllers/pages_controller_spec.rb
describe PagesController do
  render_views

  before(:each) do
    @base_title = "Ruby on Rails Tutorial Sample App"
  end
  .
  .
  .
  describe "GET 'home'" do

    describe "when not signed in" do

      before(:each) do
        get :home
      end

      it "should be successful" do
        response.should be_success
      end

      it "should have the right title" do
        response.should have_selector("title",
                                      :content => "#{@base_title} | Home")
      end
    end

    describe "when signed in" do

      before(:each) do
        @user = test_sign_in(Factory(:user))
        other_user = Factory(:user, :email => Factory.next(:email))
        other_user.follow!(@user)
      end

      it "should have the right follower/following counts" do
        get :home
        response.should have_selector("a", :href => following_user_path(@user),
                                           :content => "0 following")
        response.should have_selector("a", :href => followers_user_path(@user),
                                           :content => "1 follower")
      end
    end
  end
end

The core of this test is the expectation that the following and follower counts appear on the page, together with the right URLs:

response.should have_selector("a", :href => following_user_path(@user),
                                   :content => "0 following")
response.should have_selector("a", :href => followers_user_path(@user),
                                   :content => "1 follower")

Here we have used the named routes shown in Table 12.1 to verify that the links have the right URLs.

The application code for the stats partial is just a table inside a div, as shown in Listing 12.21.

Listing 12.21. A partial for displaying follower stats.
app/views/shared/_stats.html.erb
<% @user ||= current_user %>
<div class="stats">
  <table summary="User stats">
    <tr>
      <td>
        <a href="<%= following_user_path(@user) %>">
          <span id="following" class="stat">
            <%= @user.following.count %> following
          </span>
        </a>
      </td>
      <td>
        <a href="<%= followers_user_path(@user) %>">
          <span id="followers" class="stat">
            <%= pluralize(@user.followers.count, "follower") %>
          </span>
        </a>
      </td>
    </tr>
  </table>
</div>

Here the user following and follower counts are calculated through the associations using

@user.following.count

and

@user.followers.count

Compare these to the microposts count from Listing 11.16, where we wrote

@user.microposts.count

to count the microposts.

Since we will be including the stats on both the user show pages and the home page, the first line of Listing 12.21 picks the right one using

<% @user ||= current_user %>

As discussed in Box 9.4, this does nothing when @user is not nil (as on a profile page), but when it is (as on the Home page) it sets @user to the current user.

One final detail worth noting is the presence of CSS ids on some elements, as in

<span id="following" class="stat">
...
</span>

This is for the benefit of the Ajax implementation in Section 12.2.5, which accesses elements on the page using their unique ids.

With the partial in hand, including the stats on the Home page is easy, as shown in Listing 12.22. (This also gets the test in Listing 12.20 to pass.) The result appears in Figure 12.11.

Listing 12.22. Adding follower stats to the Home page.
app/views/pages/home.html.erb
<% if signed_in? %>
        .
        .
        .
        <%= render 'shared/user_info' %>
        <%= render 'shared/stats' %>
      </td>
    </tr>
  </table>
<% else %>
  .
  .
  .
<% end %>
home_page_follow_stats
Figure 12.11: The Home page (/) with follow stats. (full size)

We’ll render the stats partial on the profile page in a moment, but first let’s make a partial for the follow/unfollow button, as shown in Listing 12.23.

Listing 12.23. A partial for a follow/unfollow form.
app/views/users/_follow_form.html.erb
<% unless current_user?(@user) %>
  <div id="follow_form">
  <% if current_user.following?(@user) %>
    <%= render 'unfollow' %>
  <% else %>
    <%= render 'follow' %>
  <% end %>
  </div>
<% end %>

This does nothing but defer the real work to follow and unfollow partials, which need a new routes file with rules for the Relationships resource, which follows the Microposts resource example (Listing 11.21), as seen in Listing 12.24.

Listing 12.24. Adding the routes for user relationships.
config/routes.rb
SampleApp::Application.routes.draw do
  .
  .
  .
  resources :sessions,      :only => [:new, :create, :destroy]
  resources :microposts,    :only => [:create, :destroy]
  resources :relationships, :only => [:create, :destroy]
  .
  .
  .
end

The follow/unfollow partials themselves are shown in Listing 12.25 and Listing 12.26.

Listing 12.25. A form for following a user.
app/views/users/_follow.html.erb
<%= form_for current_user.relationships.
                          build(:followed_id => @user.id) do |f| %>
  <div><%= f.hidden_field :followed_id %></div>
  <div class="actions"><%= f.submit "Follow" %></div>
<% end %>
Listing 12.26. A form for unfollowing a user.
app/views/users/_unfollow.html.erb
<%= form_for current_user.relationships.find_by_followed_id(@user),
             :html => { :method => :delete } do |f| %>
  <div class="actions"><%= f.submit "Unfollow" %></div>
<% end %>

These two forms both use form_for to manipulate a Relationship model object; the main difference between the two is that Listing 12.25 builds a new relationship, whereas Listing 12.26 finds the existing relationship. Naturally, the former sends a POST request to the Relationships controller to create a relationship, while the latter sends a DELETE request to destroy a relationship. (We’ll write these actions in Section 12.2.4.) Finally, you’ll note that the follow/unfollow form doesn’t have any content other than the button, but it still needs to send the followed_id, which we accomplish with hidden_field; this produces HTML of the form

<input id="relationship_followed_id" name="relationship[followed_id]"
type="hidden" value="3" />

which puts the relevant information on the page without displaying it in the browser.

We can now include the follow form and the following statistics on the user profile page simply by rendering the partials, as shown in Listing 12.27. Profiles with follow and unfollow buttons, respectively, appear in Figure 12.12 and Figure 12.13.

Listing 12.27. Adding the follow form and follower stats to the user profile page.
app/views/users/show.html.erb
<table class="profile" summary="Profile information">
  <tr>
    <td class="main">
      <h1>
        <%= gravatar_for @user %>
        <%= @user.name %>
      </h1>
      <%= render 'follow_form' if signed_in? %>
      .
      .
      .
    </td>
    <td class="sidebar round">
      <strong>Name</strong> <%= @user.name %><br />
      <strong>URL</strong> <%= link_to user_path(@user), @user %><br />
      <strong>Microposts</strong> <%= @user.microposts.count %>
      <%= render 'shared/stats' %>
    </td>
  </tr>
</table>
profile_follow_button
Figure 12.12: A user profile with a follow button (/users/8). (full size)
profile_unfollow_button
Figure 12.13: A user profile with an unfollow button (/users/6). (full size)

We’ll get these buttons working soon enough—in fact, we’ll do it two ways, the standard way (Section 12.2.4) and using Ajax (Section 12.2.5)—but first we’ll finish the HTML interface by making the following and followers pages.

12.2.3 Following and followers pages

Pages to display user following and followers will resemble a hybrid of the user profile page and the user index page (Section 10.3.1), with a sidebar of user information (including the following stats) and a table of users. In addition, we’ll include a raster of user profile image links in the sidebar. Mockups matching these requirements appear in Figure 12.14 (following) and Figure 12.15 (followers).

following_mockup
Figure 12.14: A mockup of the user following page. (full size)
followers_mockup
Figure 12.15: A mockup of the user followers page. (full size)

Our first step is to get the following and followers links to work. We’ll follow Twitter’s lead and have both pages require user signin. For signed-in users, the pages should have links for following and followers, respectively. Listing 12.28 expresses these expectations in code.12

Listing 12.28. Test for the following and followers actions.
spec/controllers/users_controller_spec.rb
describe UsersController do
  .
  .
  .
  describe "follow pages" do

    describe "when not signed in" do

      it "should protect 'following'" do
        get :following, :id => 1
        response.should redirect_to(signin_path)
      end

      it "should protect 'followers'" do
        get :followers, :id => 1
        response.should redirect_to(signin_path)
      end
    end

    describe "when signed in" do

      before(:each) do
        @user = test_sign_in(Factory(:user))
        @other_user = Factory(:user, :email => Factory.next(:email))
        @user.follow!(@other_user)
      end

      it "should show user following" do
        get :following, :id => @user
        response.should have_selector("a", :href => user_path(@other_user),
                                           :content => @other_user.name)
      end

      it "should show user followers" do
        get :followers, :id => @other_user
        response.should have_selector("a", :href => user_path(@user),
                                           :content => @user.name)
      end
    end
  end
end

The only tricky part of the implementation is realizing that we need to add two new actions to the Users controller; based on the routes defined in Listing 12.19, we need to call them following and followers. Each action needs to set a title, find the user, retrieve either @user.following or @user.followers (in paginated form), and then render the page. The result appears in Listing 12.29.

Listing 12.29. The following and followers actions.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_filter :authenticate, :except => [:show, :new, :create]
  .
  .
  .
  def following
    @title = "Following"
    @user = User.find(params[:id])
    @users = @user.following.paginate(:page => params[:page])
    render 'show_follow'
  end

  def followers
    @title = "Followers"
    @user = User.find(params[:id])
    @users = @user.followers.paginate(:page => params[:page])
    render 'show_follow'
  end
  .
  .
  .
end

Note here that both actions make an explicit call to render, in this case rendering a view called show_follow, which we must create. The reason for the common view is that the ERb is nearly identical for the two cases, and Listing 12.30 covers them both.

Listing 12.30. The show_follow view used to render following and followers.
app/views/users/show_follow.html.erb
<table summary="Information about following/followers">
  <tr>
    <td class="main">
      <h1><%= @title %></h1>

      <% unless @users.empty? %>
        <ul class="users">
          <%= render @users %>
        </ul>
        <%= will_paginate @users %>
      <% end %>
    </td>
    <td class="sidebar round">
      <strong>Name</strong> <%= @user.name %><br />
      <strong>URL</strong> <%= link_to user_path(@user), @user %><br />
      <strong>Microposts</strong> <%= @user.microposts.count %>
      <%= render 'shared/stats' %>
      <% unless @users.empty? %>
        <% @users.each do |user| %>
          <%= link_to gravatar_for(user, :size => 30), user %>
        <% end %>
      <% end %>
    </td>
  </tr>
</table>

There’s a second detail in Listing 12.29 worth noting: in order to protect the pages for following and followers from unauthorized access, we have changed the authentication before filter to use :except instead of :only. So far in this tutorial, we have used :only to indicate which actions the filter gets applied to; with the addition of the new protected actions, the balance has shifted, and it is simpler to indicate which actions shouldn’t be filtered. We do this with the :except option to the authenticate before filter:

before_filter :authenticate, :except => [:show, :new, :create]

With that, the tests should now be passing, and the pages should render as shown in Figure 12.16 (following) and Figure 12.17 (followers).

user_following
Figure 12.16: Showing the users being followed by the current user. (full size)
user_followers
Figure 12.17: Showing the current user’s followers. (full size)

You might note that, even with the common show_follow partial, the following and followers actions still have a lot of duplication. Moreover, the show_follow partial itself shares common features with the user show page. Section 12.5 includes exercises to eliminate these sources of duplication.

12.2.4 A working follow button the standard way

Now that our views are in order, it’s time to get the follow/unfollow buttons working. Since following a user creates a relationship, and unfollowing a user destroys a relationship, this involves writing the create and destroy actions for the Relationships controller. Naturally, both actions should be protected; for signed-in users, we will use the follow! and unfollow! utility methods defined in Section 12.1.4 to create and destroy the relevant relationships. These requirements lead to the tests in Listing 12.31.

Listing 12.31. Tests for the Relationships controller actions.
spec/controllers/relationships_controller_spec.rb
require 'spec_helper'

describe RelationshipsController do

  describe "access control" do

    it "should require signin for create" do
      post :create
      response.should redirect_to(signin_path)
    end

    it "should require signin for destroy" do
      delete :destroy, :id => 1
      response.should redirect_to(signin_path)
    end
  end

  describe "POST 'create'" do

    before(:each) do
      @user = test_sign_in(Factory(:user))
      @followed = Factory(:user, :email => Factory.next(:email))
    end

    it "should create a relationship" do
      lambda do
        post :create, :relationship => { :followed_id => @followed }
        response.should be_redirect
      end.should change(Relationship, :count).by(1)
    end
  end

  describe "DELETE 'destroy'" do

    before(:each) do
      @user = test_sign_in(Factory(:user))
      @followed = Factory(:user, :email => Factory.next(:email))
      @user.follow!(@followed)
      @relationship = @user.relationships.find_by_followed_id(@followed)
    end

    it "should destroy a relationship" do
      lambda do
        delete :destroy, :id => @relationship
        response.should be_redirect
      end.should change(Relationship, :count).by(-1)
    end
  end
end

Note here how

:relationship => { :followed_id => @followed }

simulates the submission of the form with hidden field given by

<%= f.hidden_field :followed_id %>

The controller code needed to get these tests to pass is remarkably concise: we just retrieve the user followed or to be followed, and then follow or unfollow the user using the relevant utility method. The full implementation appears in Listing 12.32.

Listing 12.32. The Relationships controller.
app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
  before_filter :authenticate

  def create
    @user = User.find(params[:relationship][:followed_id])
    current_user.follow!(@user)
    redirect_to @user
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow!(@user)
    redirect_to @user
  end
end

With that, the core follow/unfollow functionality is complete, and any user can follow (or unfollow) any other user.

12.2.5 A working follow button with Ajax

Although our user following implementation is complete as it stands, we have one bit of polish left to add before starting work on the status feed. You may have noticed in Section 12.2.4 that both the create and destroy actions in the Relationships controller simply redirect back to the original profile. In other words, a user starts on a profile page, follows the user, and is immediately redirected back to the original page. It is reasonable to ask why the user needs to leave that page at all.

This is exactly the problem solved by Ajax, which allows web pages to send requests asynchronously to the server without leaving the page.13 Because the practice of adding Ajax to web forms is quite common, Rails makes Ajax easy to implement. Indeed, updating the follow/unfollow form partials is trivial: just change

form_for

to

form_for ..., :remote => true

and Rails automagically uses Ajax.14 The updated partials appear in Listing 12.33 and Listing 12.34.

Listing 12.33. A form for following a user using Ajax.
app/views/users/_follow.html.erb
<%= form_for current_user.relationships.build(:followed_id => @user.id),
             :remote => true do |f| %>
  <div><%= f.hidden_field :followed_id %></div>
  <div class="actions"><%= f.submit "Follow" %></div>
<% end %>
Listing 12.34. A form for unfollowing a user using Ajax.
app/views/users/_unfollow.html.erb
<%= form_for current_user.relationships.find_by_followed_id(@user),
             :html => { :method => :delete },
             :remote => true do |f| %>
  <div class="actions"><%= f.submit "Unfollow" %></div>
<% end %>

The actual HTML generated by this ERb isn’t particularly relevant, but you might be curious, so here’s a peek:

<form action="/relationships/117" class="edit_relationship" data-remote="true"
      id="edit_relationship_117" method="post">
  .
  .
  .
</form>

This sets the variable data-remote="true" inside the form tag, which tells Rails to allow the form to be handled by JavaScript. By using a simple HTML property instead of inserting the full JavaScript code (as in previous versions of Rails), Rails 3 follows the philosophy of unobtrusive JavaScript.

Having updated the form, we now need to arrange for the Relationships controller to respond to Ajax requests. We’ll start with a couple simple tests. Testing Ajax is quite tricky, and doing it thoroughly is a large subject in its own right, but we can get started with the code in Listing 12.35. This uses the xhr method (for “XmlHttpRequest”) to issue an Ajax request; compare to the get, post, put, and delete methods used in previous tests. We then verify that the create and destroy actions do the correct things when hit with an Ajax request. (To write more thorough test suites for Ajax-heavy applications, take a look at Selenium and Watir.)

Listing 12.35. Tests for the Relationships controller responses to Ajax requests.
spec/controllers/relationships_controller_spec.rb
describe RelationshipsController do
  .
  .
  .
  describe "POST 'create'" do
    .
    .
    .
    it "should create a relationship using Ajax" do
      lambda do
        xhr :post, :create, :relationship => { :followed_id => @followed }
        response.should be_success
      end.should change(Relationship, :count).by(1)
    end
  end

  describe "DELETE 'destroy'" do
    .
    .
    .
    it "should destroy a relationship using Ajax" do
      lambda do
        xhr :delete, :destroy, :id => @relationship
        response.should be_success
      end.should change(Relationship, :count).by(-1)
    end
  end
end

As implied by the tests, the application code uses the same create and delete actions to respond to the Ajax requests that it uses to respond to ordinary POST and DELETE HTTP requests. All we need to do is respond to a normal HTTP request with a redirect (as in Section 12.2.4) and respond to an Ajax request with JavaScript.15 The controller code appears as in Listing 12.36. (See Section 12.5 for an exercise showing an even more compact way to accomplish the same thing.)

Listing 12.36. Responding to Ajax requests in the Relationships controller.
app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
  before_filter :authenticate

  def create
    @user = User.find(params[:relationship][:followed_id])
    current_user.follow!(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow!(@user)
    respond_to do |format|
      format.html { redirect_to @user }
      format.js
    end
  end
end

This code uses respond_to to take the appropriate action depending on the kind of request.16 The syntax is potentially confusing, and it’s important to understand that in

respond_to do |format|
  format.html { redirect_to @user }
  format.js
end

only one of the lines gets executed (based on the nature of the request).

In the case of an Ajax request, Rails automatically calls a JavaScript Embedded Ruby (.js.erb) file with the same name as the action, i.e., create.js.erb or destroy.js.erb. As you might guess, the files allow us to mix JavaScript and Embedded Ruby to perform actions on the current page. It is these files that we need to create and edit in order to update the user profile page upon being followed or unfollowed.

Inside a JS-ERb file, Rails automatically provides the Prototype JavaScript helpers to manipulate the page using the Document Object Model (DOM). Prototype provides a large number of methods for manipulating the DOM, but here we will need only two. First, we will need to know about the Prototype dollar-sign syntax to access a DOM element based in its unique CSS id. For example, to manipulate the follow_form element, we will use the syntax

$("follow_form")

(Recall from Listing 12.23 that this is a div that wraps the form, not the form itself.) The second method we’ll need is update, which updates the HTML inside the relevant element with the contents of its argument. For example, to replace the entire follow form with the string "foobar", we would write

$("follow_form").update("foobar")

Unlike plain JavaScript files, JS-ERb files also allow the use of Embedded Ruby, which we apply in the create.js.erb file to update the follow form with the unfollow partial (which is what should show after a successful following) and update the follower count. The result is shown in Listing 12.37.

Listing 12.37. The JavaScript Embedded Ruby to create a following relationship.
app/views/relationships/create.js.erb
$("follow_form").update("<%= escape_javascript(render('users/unfollow')) %>")
$("followers").update('<%= "#{@user.followers.count} followers" %>')

The destroy.js.erb file is analogous (Listing 12.38). Note that, as in Listing 12.37, we must use the escape_javascript to escape out the result when inserting HTML.

Listing 12.38. The Ruby JavaScript (RJS) to destroy a following relationship.
app/views/relationships/destroy.js.erb
$("follow_form").update("<%= escape_javascript(render('users/follow')) %>")
$("followers").update('<%= "#{@user.followers.count} followers" %>')

With that, you should navigate to a user profile page and verify that you can follow and unfollow without a page refresh.

Using Ajax in Rails is a large and fast-moving subject, so we’ve only been able to scratch the surface here, but (as with the rest of the material in this tutorial) our treatment gives you a good foundation for more advanced resources. It’s especially worth noting that, in addition to Prototype, the JavaScript framework jQuery has gotten a lot of traction in the Rails community. In fact, as discussed in Chapter 13, in Rails 3.1 jQuery is the default. Implementing the Ajax functions from this section using jQuery is left as an exercise; see Section 12.5 and especially Section 13.1.4.2.

12.3 The status feed

We come now to the pinnacle of our sample application: the status feed. Appropriately, this section contains some of the most advanced material in the entire tutorial. Making the status feed involves assembling an array of the microposts from the users being followed by the current user, along with the current user’s own microposts. To accomplish this feat, we will need some fairly advanced Rails, Ruby, and even SQL programming techniques.

Because of the heavy lifting ahead, it’s especially important to have a sense of where we’re going. A mockup of the final user status feed, which builds on the proto-feed from Section 11.3.3, appears in Figure 12.18.

home_page_feed_mockup
Figure 12.18: A mockup of a user’s Home page with a status feed. (full size)

12.3.1 Motivation and strategy

The basic idea behind the feed is simple. Figure 12.19 shows a sample microposts database table and the resulting feed. The purpose of a feed is to pull out the microposts whose user ids correspond to the users being followed by the current user (and the current user itself), as indicated by the arrows in the diagram.

user_feed
Figure 12.19: The feed for a user (id 1) following users 2, 7, 8, and 10.

Since we need a way to find all the microposts from users followed by a given user, we’ll plan on implementing a method called from_users_followed_by, which we will use as follows:

Micropost.from_users_followed_by(user)

Although we don’t yet know how to implement it, we can already write tests for from_users_followed_by, as seen in Listing 12.39.

Listing 12.39. Tests for Micropost.from_users_followed_by.
spec/models/micropost_spec.rb
describe Micropost do
  .
  .
  .
  describe "from_users_followed_by" do

    before(:each) do
      @other_user = Factory(:user, :email => Factory.next(:email))
      @third_user = Factory(:user, :email => Factory.next(:email))

      @user_post  = @user.microposts.create!(:content => "foo")
      @other_post = @other_user.microposts.create!(:content => "bar")
      @third_post = @third_user.microposts.create!(:content => "baz")

      @user.follow!(@other_user)
    end

    it "should have a from_users_followed_by class method" do
      Micropost.should respond_to(:from_users_followed_by)
    end

    it "should include the followed user's microposts" do
      Micropost.from_users_followed_by(@user).should include(@other_post)
    end

    it "should include the user's own microposts" do
      Micropost.from_users_followed_by(@user).should include(@user_post)
    end

    it "should not include an unfollowed user's microposts" do
      Micropost.from_users_followed_by(@user).should_not include(@third_post)
    end
  end
end

The key here is building the associations in the before(:each) block and then checking all three requirements: microposts for followed users and the user itself are included, but a post from an unfollowed user is not.

The feed itself lives in the User model (Section 11.3.3), so we should add an additional test to the User model specs from Listing 11.31, as shown in Listing 12.40. (Note that we’ve switched here from using include?, as seen in Listing 11.31, to the more compact include convention introduced in Listing 12.12.)

Listing 12.40. The final tests for the status feed.
spec/models/user_spec.rb
describe User do
  .
  .
  .
  describe "micropost associations" do
    .
    .
    .
    describe "status feed" do

      it "should have a feed" do
        @user.should respond_to(:feed)
      end

      it "should include the user's microposts" do
        @user.feed.should include(@mp1)
        @user.feed.should include(@mp2)
      end

      it "should not include a different user's microposts" do
        mp3 = Factory(:micropost,
                      :user => Factory(:user, :email => Factory.next(:email)))
        @user.feed.should_not include(mp3)
      end

      it "should include the microposts of followed users" do
        followed = Factory(:user, :email => Factory.next(:email))
        mp3 = Factory(:micropost, :user => followed)
        @user.follow!(followed)
        @user.feed.should include(mp3)
      end
    end
    .
    .
    .
  end
end

Implementing the feed will be easy; we will simply defer to Micropost.from_users_followed_by, as shown in Listing 12.41.

Listing 12.41. Adding the completed feed to the User model.
app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  def feed
    Micropost.from_users_followed_by(self)
  end
  .
  .
  .
end

12.3.2 A first feed implementation

Now it’s time to implement Micropost.from_users_followed_by, which for simplicity we’ll just refer to as “the feed”. Since the final result is rather intricate, we’ll build up to the final feed implementation by introducing one piece at a time.

The first step is to think of the kind of query we’ll need. What we want to do is select from the microposts table all the microposts with ids corresponding to the users being followed by a given user (or the user itself). We might write this schematically as follows:

SELECT * FROM microposts
WHERE user_id IN (<list of ids>) OR user_id = <user id>

In writing this code, we’ve guessed that SQL supports an IN keyword that allows us to test for set inclusion. (Happily, it does.)

Recall from the proto-feed in Section 11.3.3 that Active Record uses the where method to accomplish the kind of select shown above, as illustrated in Listing 11.32. There, our select was very simple; we just picked out all the microposts with user id corresponding to the current user:

Micropost.where("user_id = ?", id)

Here, we expect it to be more complicated, something like

where("user_id in (#{following_ids}) OR user_id = ?", user)

(Here we’ve used the Rails convention of user instead of user.id in the condition; Rails automatically uses the id. We’ve also omitted the leading Micropost. since we expect this method to live in the Micropost model itself.)

We see from these conditions that we’ll need an array of ids that a given user is following (or something equivalent). One way to do this is to use Ruby’s map method, available on any “enumerable” object, i.e., any object (such as an Array or a Hash) that consists of a collection of elements.17 We saw an example of this method in Section 4.3.2; it works like this:

$ rails console
>> [1, 2, 3, 4].map { |i| i.to_s }
=> ["1", "2", "3", "4"]

Situations like the one illustrated above, where the same method (e.g., to_s) gets called on each element, are common enough that there’s a shorthand notation using an ampersand & and a symbol corresponding to the method:18

>> [1, 2, 3, 4].map(&:to_s)
=> ["1", "2", "3", "4"]

We can use this notation to construct the necessary array of followed user ids by calling id on each element in user.following. For example, for the first user in the database this array appears as follows:

>> User.first.following.map(&:id)
=> [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51]

In fact, because this sort of construction is so useful, Active Record provides it by default:

>> User.first.following_ids
=> [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51]

Here the following_ids method is synthesized by Active Record based on the has_many :following association (Listing 12.11); the result is that we need only append _ids to the association name to get the ids corresponding to the user.following collection.

At this point, you might guess that code like

Micropost.from_users_followed_by(user)

will involve a class method in the Micropost class (a construction last seen in the User class in Section 7.12). A proposed implementation along these lines appears in Listing 12.42.

Listing 12.42. A first cut at the from_users_followed_by method.
app/models/micropost.rb
class Micropost < ActiveRecord::Base
  .
  .
  .
  def self.from_users_followed_by(user)
    following_ids = user.following_ids
    where("user_id IN (#{following_ids}) OR user_id = ?", user)
  end
end

Although the discussion leading up to Listing 12.42 was couched in hypothetical terms, it actually works! In fact, it might be good enough for most practical purposes. But it’s not the final implementation; see if you can make a guess about why not before moving on to the next section. (Hint: What if a user is following 5000 other users?)

By the way, if we were to use the method in Listing 12.42 for the final implementation, there’s a nice refactoring we could make. The where method can take a hash argument with key :user_id and value equal to an array of users. In this case, we need an array of all the users being followed, as well as the user itself; we can arrange this by using the push method (mentioned in Section 4.3.1) on the following association, as seen in Listing 12.43.

Listing 12.43. A refactored from_users_followed_by method.
app/models/micropost.rb
class Micropost < ActiveRecord::Base
  .
  .
  .
  def self.from_users_followed_by(user)
    where(:user_id => user.following.push(user))
  end
end

12.3.3 Scopes, subselects, and a lambda

As hinted at in the last section, the feed implementation in Section 12.3.2 doesn’t scale well when the number of microposts in the feed is large, as would likely happen if a user were following, say, 5000 other users. In this section, we’ll reimplement the status feed in a way that scales better with the number of followed users.

There are a couple of problems with the code in Section 12.3.2. First, the expression

following_ids = user.following_ids

pulls all the followed users into memory, and creates an array the full length of the following list. Since the condition in Listing 12.42 actually just checks inclusion in a set, there must be a more efficient way to do this, and indeed SQL is optimized for just such set operations. Second, the method in Listing 12.42 always pulls out all the microposts and sticks them into a Ruby array. Although these microposts are paginated in the view (Listing 11.33), the array is still full-sized.19 What we really want is honest pagination that only pulls out 30 elements at a time.

The solution to both problems involves converting the feed from a class method to a scope, which is a Rails method for restricting database selects based on certain conditions. For example, to arrange for a method to select all the administrative users in our application, we could add a scope to the User model as follows:

class User < ActiveRecord::Base
  .
  .
  .
  scope :admin, where(:admin => true)
  .
  .
  .
end

As a result of this scope, the code

User.admin

would return an array of all the site admins.

The main reason scopes are better than plain class methods is that they can be chained with other methods, so that, for example,

User.admin.paginate(:page => 1)

actually paginates the admins in the database; if (for some odd reason) the site has 100 administrators, the code above will still only pull out the first 30.

The scope for the feed is a bit more complex than the one illustrated above: it needs an argument, namely, the user whose feed we need to generate. We can do this with an anonymous function, or lambda (discussed in Section 8.4.2), as shown in Listing 12.44.20

Listing 12.44. Improving from_users_followed_by.
app/models/micropost.rb
class Micropost < ActiveRecord::Base
  .
  .
  .
  default_scope :order => 'microposts.created_at DESC'

  # Return microposts from the users being followed by the given user.
  scope :from_users_followed_by, lambda { |user| followed_by(user) }

  private

    # Return an SQL condition for users followed by the given user.
    # We include the user's own id as well.
    def self.followed_by(user)
      following_ids = user.following_ids
      where("user_id IN (#{following_ids}) OR user_id = :user_id",
            { :user_id => user })
    end
end

Since the conditions on the from_users_followed_by scope are rather long, we have defined an auxiliary function to handle it:

def self.followed_by(user)
  following_ids = user.following_ids
  where("user_id IN (#{following_ids}) OR user_id = :user_id",
        { :user_id => user })
end

As preparation for the next step, we have replaced

where("... OR user_id = ?", user)

with the equivalent

where("... OR user_id = :user_id", { :user_id => user })

The question mark syntax is fine, but when we want the same variable inserted in more than one place, the second syntax, using a hash, is more convenient.

The above discussion implies that we will be adding a second occurrence of user_id in the SQL query, and indeed this is the case. We can replace the Ruby code

following_ids = user.following_ids

with the SQL snippet

following_ids = %(SELECT followed_id FROM relationships
                  WHERE follower_id = :user_id)

(See Box 12.1 for an explanation of the %() syntax.) This code contains an SQL subselect, and internally the entire select for user 1 would look something like this:

SELECT * FROM microposts
WHERE user_id IN (SELECT followed_id FROM relationships
                  WHERE follower_id = 1)
      OR user_id = 1

This subselect arranges for all the set logic to be pushed into the database, which is more efficient.21

With this foundation, we are ready for an efficient feed implementation, as seen in Listing 12.45.

Listing 12.45. The final implementation of from_users_followed_by.
app/models/micropost.rb
class Micropost < ActiveRecord::Base
  .
  .
  .
  default_scope :order => 'microposts.created_at DESC'

  # Return microposts from the users being followed by the given user.
  scope :from_users_followed_by, lambda { |user| followed_by(user) }

  private

    # Return an SQL condition for users followed by the given user.
    # We include the user's own id as well.
    def self.followed_by(user)
      following_ids = %(SELECT followed_id FROM relationships
                        WHERE follower_id = :user_id)
      where("user_id IN (#{following_ids}) OR user_id = :user_id",
            { :user_id => user })
    end
end

This code has a formidable combination of Rails, Ruby, and SQL, but it does the job, and does it well.22

12.3.4 The new status feed

With the code in Listing 12.45, our status feed is complete. As a reminder, the code for the Home page appears in Listing 12.46; this code creates a paginated feed of the relevant microposts for use in the view, as seen in Figure 12.20.23 Note that the paginate method actually reaches all the way into the Micropost model method in Listing 12.45, arranging to pull out only 30 microposts at a time from the database.24

Listing 12.46. The home action with a paginated feed.
app/controllers/pages_controller.rb
class PagesController < ApplicationController

  def home
    @title = "Home"
    if signed_in?
      @micropost = Micropost.new
      @feed_items = current_user.feed.paginate(:page => params[:page])
    end
  end
  .
  .
  .
end
home_page_with_feed
Figure 12.20: The Home page with a working status feed. (full size)

12.4 Conclusion

With the addition of the status feed, we’ve finished the core sample application for Ruby on Rails Tutorial. This application includes examples of all the major features of Rails, including models, views, controllers, templates, partials, filters, validations, callbacks, has_many/belongs_to and has_many :through associations, security, testing, and deployment. Despite this impressive list, there is still much to learn about Rails. As a first step in this process, this section contains some suggested extensions to the core application, as well as suggestions for further learning.

Before moving on to tackle any of the application extensions, it’s a good idea to merge in your changes and deploy the application:

$ git add .
$ git commit -m "Added user following"
$ git checkout master
$ git merge following-users
$ git push heroku
$ heroku rake db:migrate

12.4.1 Extensions to the sample application

The proposed extensions in this section are mostly inspired either by general features common to web applications, such as password reminders and email confirmation, or features specific to our type of sample application, such as search, replies, and messaging. Implementing one or more of these application extensions will help you make the transition from following a tutorial to writing original applications of your own.

Don’t be surprised if it’s tough going at first; the blank slate of a new feature can be quite intimidating. To help get you started, I can give two pieces of general advice. First, before adding any feature to a Rails application, take a look at the Railscasts archive to see if Ryan Bates has already covered the subject.25 If he has, watching the relevant Railscast first will often save you a ton of time. Second, always do extensive Google searches on your proposed feature to find relevant blog posts and tutorials. Web application development is hard, and it helps to learn from the experience (and mistakes) of others.

Many of the following features are quite challenging, and I have given some hints about the tools you might need to implement them. Even with hints, they are much more difficult than the book’s end-of-chapter exercises, so don’t be discouraged if you can’t solve them without considerable effort. Due to time constraints, I am not available for one-on-one assistance, but if there is sufficient interest I might release standalone article/screencast bundles on some of these extensions in the future; go to the main Rails Tutorial website at http://railstutorial.org/ and subscribe to the news feed to get the latest updates.

Replies

Twitter allows users to make “@replies”, which are microposts whose first characters are the user’s login preceded by the @ sign. These posts only appear in the feed of the user in question or users following that user. Implement a simplified version of this, restricting @replies to appear only in the feeds of the recipient and the sender. This might involve adding an in_reply_to column in the microposts table and an extra including_replies scope to the Micropost model.

Since our application lacks unique user logins, you will also have to decide on a way to represent users. One option is to use a combination of the id and the name, such as @1-michael-hartl. Another is to add a unique username to the signup process and then use it in @replies.

Messaging

Twitter supports direct (private) messaging by prefixing a micropost with the letter “d”. Implement this feature for the sample application. The solution will probably involve a Message model and a regular expression match on new microposts.

Follower notifications

Implement a feature to send each user an email notification when they gain a new follower. Then make the notification optional, so that users can opt out if desired.

Among other things, adding this feature requires learning how to send mail with Rails. There is a Railscast on sending email to get you started. Beware that the main Rails library for sending email, Action Mailer, has gotten a major overhaul in Rails 3, as seen in the Railscast on Action Mailer in Rails 3.

Password reminders

Currently, if our application’s users forget their passwords, they have no way to retrieve them. Because of the one-way secure password hashing in Chapter 7, our application can’t email the user’s password, but it can send a link to a reset form. Introduce a PasswordReminders resource to implement this feature. For each reset, you should create a unique token and email it to the user. Visiting a URL with the token should then allow them to reset their password to a value of their choice.

Signup confirmation

Apart from an email regular expression, the sample application currently has no way to verify the validity of a user’s email address. Add an email address verification step to confirm a user’s signup. The new feature should create users in an inactive state, email the user an activation URL, and then change the user to an active state when the URL gets hit. You might want to read up on state machines in Rails to help you with the inactive/active transition.

RSS feed

For each user, implement an RSS feed for their microposts. Then implement an RSS feed for their status feed, optionally restricting access to that feed using an authentication scheme. The Railscast on generating RSS feeds will help get you started.

REST API

Many web sites expose an Application Programmer Interface (API) so that third-party applications can get, post, put, and delete the application’s resources. Implement such a REST API for the sample application. The solution will involve adding respond_to blocks (Section 12.2.5) to many of the application’s controller actions; these should respond to requests for XML. Be careful about security; the API should only be accessible to authorized users.

Search

Currently, there is no way for users to find each other than paging through the user index or viewing the feeds of other users. Implement a search feature to remedy this. Then add another search feature for microposts. The Railscast on simple search forms will help get you started. If you deploy using a shared host or a dedicated server, I suggest using Thinking Sphinx (following the Railscast on Thinking Sphinx). If you deploy on Heroku, you should follow the Heroku full text search instructions.

12.4.2 Guide to further resources

There are a wealth of Rails resources in stores and on the web—indeed, the supply is so rich that it can be overwhelming. The good news is that, having gotten this far, you’re ready for almost anything else out there. Here are some suggestions for further learning:

  • Ruby on Rails Tutorial screencasts: I have prepared a full-length screencast course based on this book. In addition to covering all the material in the book, the screencasts are filled with tips, tricks, and the kind of see-how-it’s-done demos that are hard to capture in print. They are available on the Ruby on Rails Tutorial website, through Safari Books Online, and through InformIT.
  • Railscasts: It’s hard to overemphasize what a great resource the Railscasts are. I suggest starting by visiting the Railscasts episode archive and clicking on subjects that catch your eye.
  • Scaling Rails: One topic we’ve hardly covered in the Ruby on Rails Tutorial book is performance, optimization, and scaling. Luckily, most sites will never run into serious scaling issues, and using anything beyond plain Rails is probably premature optimization. If you do run into performance issues, the Scaling Rails series from Gregg Pollack of Envy Labs is a great place to start. I also recommend investigating the site monitoring applications Scout and New Relic.26 And, as you might suspect by now, there are Railscasts on many scaling subjects, including profiling, caching, and background jobs.
  • Ruby and Rails books: As mentioned in Chapter 1, I recommend Beginning Ruby by Peter Cooper, The Well-Grounded Rubyist by David A. Black, and The Ruby Way by Hal Fulton for further Ruby learning, and The Rails 3 Way by Obie Fernandez for more about Rails.
  • PeepCode: I mentioned several commercial screencasters in Chapter 1, but the only one I have extensive experience with is PeepCode. The screencasts at PeepCode are consistently high-quality, and I warmly recommend them.

12.5 Exercises

  1. Add tests for dependent :destroy in the Relationship model (Listing 12.5 and Listing 12.17) by following the example in Listing 11.11.
  2. The respond_to method seen in Listing 12.36 can actually be hoisted out of the actions into the Relationships controller itself, and the respond_to blocks can be replaced with a Rails method called respond_with. Prove that the resulting code, shown in Listing 12.47, is correct by verifying that the test suite still passes. (For details on this method, do a Google search on “rails respond_with”.)
  3. The following and followers actions in Listing 12.29 still have considerable duplication. Verify that the show_follow method in Listing 12.48 eliminates this duplication. (See if you can infer what the send method does, as in, e.g., @user.send(:following).)
  4. Refactor Listing 12.30 by adding partials for the code common to the following/followers pages, the Home page, and the user show page.
  5. Following the model in Listing 12.20, write tests for the stats on the profile page.
  6. Write an integration test for following and unfollowing a user.
  7. Rewrite the Ajax methods from Section 12.2.5 using jQuery in place of Prototype. Hint: You might want to read the jQuery section of Mikel Lindsaar’s blog post about jQuery, RSpec, and Rails 3. Also see Section 13.1.4.2, which discusses jQuery in the context of Rails 3.1.
Listing 12.47. A compact refactoring of Listing 12.36.
class RelationshipsController < ApplicationController
  before_filter :authenticate

  respond_to :html, :js

  def create
    @user = User.find(params[:relationship][:followed_id])
    current_user.follow!(@user)
    respond_with @user
  end

  def destroy
    @user = Relationship.find(params[:id]).followed
    current_user.unfollow!(@user)
    respond_with @user
  end
end
Listing 12.48. Refactored following and followers actions.
app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def following
    show_follow(:following)
  end

  def followers
    show_follow(:followers)
  end

  def show_follow(action)
    @title = action.to_s.capitalize
    @user = User.find(params[:id])
    @users = @user.send(action).paginate(:page => params[:page])
    render 'show_follow'
  end
  .
  .
  .
end
  1. The photographs in the mockup tour are from http://www.flickr.com/photos/john_lustig/2518452221/ and http://www.flickr.com/photos/30775272@N05/2884963755/
  2. For simplicity, Figure 12.6 suppresses the following table’s id column. 
  3. Unfortunately, Rails uses connection for a database connection, so introducing a Connection model leads to some rather subtle bugs. (I learned this the hard way when developing Insoshi.) 
  4. Indeed, this construction is so characteristic of Rails that well-knows Rails programmer Josh Susser used it as the name of his geek blog
  5. Technically, Rails uses the underscore method to convert the class name to an id. For example, "FooBar".underscore is foo_bar, so the foreign key for a FooBar object would be foo_bar_id. (Incidentally, the inverse of underscore is camelize, which converts camel_case to CamelCase.) 
  6. If you’ve noticed that followed_id also identifies a user, and are concerned about the asymmetric treatment of followed and follower, you’re ahead of the game. We’ll deal with this issue in Section 12.1.5
  7. This follow! method should always work, so (following the model of create! and save!) we indicate with an exclamation point that an exception will be raised on failure. 
  8. Once you have a lot of experience modeling a particular domain, you can often guess such utility methods in advance, and even when you can’t you’ll often find yourself writing them to make the tests cleaner. In this case, though, it’s OK if you wouldn’t have guessed them. Software development is usually an iterative process—you write code until it starts getting ugly, and then you refactor it—but for brevity the tutorial presentation is streamlined a bit. 
  9. The authenticate_with_salt method is included simply to orient you within the User model file. 
  10. The unfollow! method doesn’t raise an exception on failure—in fact, I don’t even know how Rails indicates a failed destroy—but we use an exclamation point to maintain the follow!/unfollow! symmetry. 
  11. You might notice that sometimes we access id explicitly, as in followed.id, and sometimes we just use followed. I shame to admit that my usual algorithm for telling when to leave it off is to see if it works without .id, and then add .id if it breaks. 
  12. Everything in Listing 12.28 has been covered elsewhere in this tutorial, so this is a good exercise in reading code. 
  13. Because it is nominally an acronym for asynchronous JavaScript and XML, Ajax is sometimes misspelled “AJAX”, even though the original Ajax article spells it as “Ajax” throughout. 
  14. This only works if JavaScript is enabled in the browser, but it degrades gracefully, working exactly as in Section 12.2.4 if JavaScript is disabled. 
  15. At this point you will have to include the default Prototype JavaScript Library into your Rails application as in Listing 10.39 if you have not done so already. 
  16. There is no relationship between this respond_to and the respond_to used in the RSpec examples. 
  17. The main requirement is that enumerable objects must implement an each method to iterate through the collection. 
  18. This notation actually started as an extension Rails made to the core Ruby language; it was so useful that it has now been incorporated into Ruby itself. How cool is that? 
  19. Calling paginate on an Array object converts it into a WillPaginate::Collection object, but that doesn’t help us much since the entire array has already been created in memory. 
  20. A function bundled with a piece of data (a user, in this case) is known as a closure, which we encountered briefly in the discussion of blocks in Section 4.3.2
  21. For a more advanced way to create the necessary subselect, see the blog post “Hacking a subselect in ActiveRecord”. 
  22. Of course, even the subselect won’t scale forever. For bigger sites, you would probably need to generate the feed asynchronously using a background job. Such scaling subtleties are beyond the scope of this tutorial, but the Scaling Rails screencasts are a good place to start. 
  23. In order to make a prettier feed for Figure 12.20, I’ve added a few extra microposts by hand using the Rails console. 
  24. You can verify this by examining the SQL statements in the development server log file. (The Rails Tutorial screencasts will cover such subtleties in more depth.) 
  25. My only reservation about Railscasts is that they often omit the tests. This is probably necessary to keep the episodes nice and short, but you could get the wrong idea about the importance of tests. Once you’ve watched the relevant Railscast to get a basic idea of how to proceed, I suggest writing the new feature using test-driven development. 
  26. In addition to being a clever phrase—new relic being a contradiction in terms—New Relic is also an anagram for the name of the company’s founder, Lew Cirne.