Welcome

Welcome to the LA Ruby Study Group Rails Tutorial Workshop.

We are going to work through the Rails Tutorial book. You will have already worked through the install steps last weekend. Tonight we will be starting right in with Chapter 3 - Mostly Static Pages.

We will be skipping the testing portions of the book. In the long run, testing saves time you would spend debugging or doing ad hoc testing - but in the short run it takes time.

Our Sample App

  cd _wherever_you_want_to_work_
  rails new sample_app

  app/   - this is where your application lives
     controllers/
     helpers/
     models/
     views/
  config/  - application configuration, e.g. database.yml
  db/migrations/ - database-agnostic schema definition
  log/     - log files. Look here for debugging information
  public/  - images, stylesheets, javascript
  Gemfile  - what gems should we load for this application? 
  .gitignore -  Lists of files we don't want version controlled      

Start Your Server

  cd sample_app
  bundle install
  rails server	
  => Booting WEBrick
  => Rails 3.0.9 application starting on http://0.0.0.0:3000
  => Call with -d to detach
  => Ctrl-C to shutdown server      

In your web browser, got to http://localhost:3000

Using Git

Version control is a good development practice. It is also the tool we are going to use to transfer the code you have on your local machine to Heroku - a web host where the rest of the world can see your handiwork.

  Initial (one time) setup: 
  git config --global user.name "Your Name"
  git config --global user.name "Your Name"

  git init
  # Possibly edit the .gitignore file
  git add . 
  git commit -m "Bare rails project"      

Deploy

Everyone signed up for a free Heroku account, right? https://api.heroku.com/signup

  gem list heroku

  heroku keys:add
  heroku create
  git push heroku master

  heroku open      

Don't worry about the fact the "About your application's environment" link doesn't work on Heroku. That's normal. We'll be replacing that page with something more interesting soon.

Static Pages

Any page or file placed within the public/ directory will be served directly from that location without passing through the rails routing system. For example, let's create a file public/hello.html

That's nice. But it has all the disadvantages of a static web page. For example, it must contain all of the styling information for the page. And if that changes, it needs to be changed on every page. That violates one of Rails' guiding principles: Don't Repeat Yourself

Rails can offer you a better option - using the Rails templating system to apply a common layout.

Not Really Static Pages

  rails generate controller Pages home contact      
      create  app/controllers/pages_controller.rb
       route  get "pages/contact"
       route  get "pages/home"
      invoke  erb
      create    app/views/pages
      create    app/views/pages/home.html.erb
      create    app/views/pages/contact.html.erb      

This will have generated a controller named Pages and added 2 actions to it: home and contact. It has also added routes and pages. Let's look at them.

Common Layout

Let's take advantage of the site layout file. Let's add a title to each page.

  class PagesController < ApplicationController
    def home
      @title = "Home"
    end
  end      

Views: html + ruby. The text between <% and %> is evaluated as ruby. If there is an equal sign, the result of the ruby is placed in the html: <%= 2+2 %> yields 4. The value of instance variables can be placed in a view.

  <title><%= @title %></title>     

A Sliver of Ruby

  module ApplicationHelper

    # Return a title on a per-page basis.
    def title
      base_title = "Ruby on Rails Tutorial Sample App"
      if @title.nil?
	base_title
      else
	"#{base_title} | #{@title}"
      end
    end
  end  

A Bit More Ruby

  # Array's 
  a = [23, 43, "foo", "zombies"]
  puts a[0] 
  => 23
  a.length 
  => 4

  # Hashes
  user = { "first_name" => "Michael", "last_name" => "Hartl" }
  user["last_name"]
  => "Hartl"
  user["email"]
  => nil

  # Hashes with symbols
  user = { :first_name => "Michael", :last_name => "Hartl" }
  user[:first_name]
  => "Hartl"      

Styling with Blueprint CSS

Download the latest version of Blueprint CSS from http://www.blueprintcss.org/ Unzip or untar it. Then copy the subdirectory "blueprint" into your public/stylesheets/ directory.

  cd joshuaclayton-blueprint-css-*version number*        
  cp -r blueprint $RAILS_ROOT/public/stylesheets/   
  cd $RAILS_ROOT
  ls public/stylesheets
  blueprint/ 

  # Edit your application layout file to add the screen and print
  styles - and take care of a few IE issues:
  <html>
    <head>
      <title><%= @title %></title>
      <%= csrf_meta_tag %>
      <!--[if lt IE 9]>
      <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
      <![endif]-->    
      <%= stylesheet_link_tag 'blueprint/screen', :media => 'screen' %>
      <%= stylesheet_link_tag 'blueprint/print',  :media => 'print' %>
      <!--[if lt IE 8]><%= stylesheet_link_tag 'blueprint/ie' %><![endif]-->
      <%= stylesheet_link_tag 'custom', :media => 'screen' %>
    </head> 

Spicing Up Your Application Layout

Let's start by looking at Michael's wireframe. So, there is going to be a logo at the top - and some standard links in the upper left. Let's put those in.

First, let's copy the logo from http://railstutorial.org/images/sample_app/logo.png Place that in your public/images directory. Then use the image tag to put it into your page:

<%= image_tag("logo.png", :alt => "Sample App", :class => "round") %>        

And add the links at the top of the page:

  <nav class="round">
    <ul>
      <li><%= link_to "Home", '#' %></li>
      <li><%= link_to "Help", '#' %></li>
      <li><%= link_to "Sign in", '#' %></li>
    </ul>
  </nav>       

Routes

"Normal" web sites serve pages from subdirectories of the document root. Rails uses this style of url <-> file matching for items under public/. But for the most part urls are matched to controllers using routes configured in config/routes.rb

SampleApp::Application.routes.draw do
  match '/contact', :to => 'pages#contact'
  match '/about',   :to => 'pages#about'
  match '/help',    :to => 'pages#help'

  root :to => 'pages#home'
end  

To reference those routes, don't use the url form ('/about') use the helper functions: about_path or about_url.

<%= link_to "About", about_path %>  

Partials

OK things are starting to look pretty good. We have everything except the footer in place. But have you noticed how complicated the layout file is starting to look? That big block of stylesheet references is a lot of verbage for something so simple. Let's hide that complexity from application.html.erb by putting it in its own file.

  <%= render "layouts/stylesheets" %>      
  
  # inserts the html from app/views/layouts/_stylesheets.html.erb   

Let's do the same for our header and footer.

Sign up now!

Now for that big green button. Sign up is the same thing as making a new user. Let's make a new controller for users - with an action to create a new user.

  rails generate controller Users new      

That doesn't really do anything yet - but it should work. Let's edit the sign up link. But what is the name of the new user route that was added?

  rake routes
  users_new GET /users/new(.:format) {:controller=>"users", :action=>"new"}
    contact     /contact(.:format)   {:controller=>"pages", :action=>"contact"}
      about     /about(.:format)     {:controller=>"pages", :action=>"about"}
       help     /help(.:format)      {:controller=>"pages", :action=>"help"}
       root     /(.:format)          {:controller=>"pages", :action=>"home"}      

So we could use users_new_path OR we can add a named route:

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

User Modeling

Rails now supports several different methods of storing data - but the default is still ActiveRecord - an ORM (object relational mapper) that can store data in any one of several types of databases.

  rails generate model User name:string email:string  

Notice models are singular, controllers (and table names) are plural.

Look at the migration file. Data definition in bite-sized steps - and in ruby so they are database agnositic (as long as you don't need a specialized feature of your database). Notice t.timestamps - that adds special columns to your table: updated_at and created_at. Callbacks will updated those automatically when a record is created or updated.

  class CreateUsers < ActiveRecord::Migration
    def self.up
      create_table :users do |t|
	t.string :name
	t.string :email

	t.timestamps
      end
    end

    def self.down
      drop_table :users
    end
  end  
  bundle exec rake db:migrate   

Let's look at the table using either SQLite Database Browser or the Firefox SQlite Manager. Note the id column is added automatically.

The User Class

So let's look at the Ruby class that represents our User.

  class User < ActiveRecord::Base
  end      

But there's nothing there?! Does it do anything yet? Yes, actually it does. Let's break out the rails console and see. With only that 1 line, we can create users, store them in the database, find them again, edit or get rid of them. Wow!

Sure would be nice if we could see what attributes our User objects have without having to look in the database.

We can, using the annotate gem. Do the following:

  # Edit your Gemfile 
  gem "annotate", :group => :development
  bundle install
  bundle exec annotate --position before
  # Now look at your user.rb model file. Much better!      

Validations

Let's also add some data validations:

  class User < ActiveRecord::Base
    attr_accessible :name, :email
    validates :name, :presence => true
  end      

With that restriction, we get automatic testing of it whenever we try to save the record - and a number of convenience methods such as a_user.valid? and a_user.errors.full_messages

# full set of validations we want
  email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i

  validates :name,  :presence => true,
                    :length   => { :maximum => 50 }
  validates :email, :presence => true,
                    :format   => { :with => email_regex },
		    :uniqueness => { :case_sensitive => false } 
# add unique index on email
rails generate migration add_email_uniqueness_index

class AddEmailUniquenessIndex < ActiveRecord::Migration
  def self.up
    add_index :users, :email, :unique => true
  end

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

User's home page

Let's make a page to show what we know about a user. The rails default for such a page is called show:

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

And we should create the file app/views/users/show.html.erb. Hmmm but it still doesn't work because there is no route. Let's add the following - and then use rake routes to see what that did.

  resources :users  

Password Fields

So we created some users - but without passwords how do I know you are who you say you are?

A password and password confirmation field are pretty standard for most signup forms on the web. But we don't want to store the password confirmation - in fact we don't really want to store the password, at least not the way you typed it in. For security, it should be encrypted. So what we need are some "virtual attributes". (From the point of view of the User class, they are real attributes but from ActiveRecord's perspective, they aren't.)

attr_accessor :password

# Automatically create the virtual attribute 'password_confirmation'.
validates :password, :presence     => true,
                     :confirmation => true,
                     :length       => { :within => 6..40 }      

Password Migration

OK but where are we going to store the password? In a field called encrypted_password. (We'll show you how the password you type in becomes an encrypted password in a minute.)

rails generate migration add_password_to_users encrypted_password:string      

Look at the generated migration file. Impressive, yes? That is rail's "Convention over Configuration" at work.

class AddPasswordToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :encrypted_password, :string
  end

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

Saving the Encrypted Password

So we have validated our password and password confirmation fields. But how do we store the password into the encrypted_password field? We'll use an ActiveRecord callback to automate this process:

class User < ActiveRecord::Base
...
  before_save :encrypt_password

  private

    def encrypt_password
      self.encrypted_password = encrypt(password)
    end

    def encrypt(string)
      string # Only a temporary implementation!
    end
end      

Note the keyword "private" and assignment using self.encrypted_password.

Checking the Password

So ignore for now that we are not really encrypting passwords. How will we check the password you supply on a login form?

  # Encrypt the password you just submitted and compare it to the encrypted one
  def has_password?(submitted_password)
    encrypted_password == encrypt(submitted_password)
  end      

Password Encryption

The standard way of storing passwords is to use a one-way hash. One way means that you can't deduce the password from knowing the encrypted password. However with enough time, you can compute likely hashes and compare them until you find a match. To make that harder, it is recommended to use a salt - an extra piece of data that gets encrypted with the password.

So let's start by creating a column for the salt:

  rails generate migration add_salt_to_users salt:string      

Now for the full encryption functions:

    def encrypt_password
      self.salt = make_salt unless has_password?(password)
      self.encrypted_password = encrypt(password)
    end

    def encrypt(string)
      secure_hash("#{salt}--#{string}")
    end

    def make_salt
      secure_hash("#{Time.now.utc}--#{password}")
    end

    def secure_hash(string)
      Digest::SHA2.hexdigest(string)
    end      

User Authentication

So to authenticate a user, we need to look up the user record, grab the salt, encrypt the supplied password, and compare it to the one the user just typed in. Not terribly complex - but wouldn't it be nice to have that available as a single action so you don't have to repeat those steps everywhere you want to check on a user? Let's make a method to do that.

The methods we have created up to this point have been instance methods - they act on a particular user. But we don't have a specific user yet - that's what we are trying to figure out. So we'll need a class method.

class User < ActiveRecord::Base

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

# To use this:
@user = User.authenticate(email, password)      

Gravatars

Let's spruce up our user display page a bit. Have you seen blog comments where the comment had someone's picture next to it? Fancy, huh? But actually pretty simple because there is a web service you can use to add those images to your pages: http://en.gravatar.com/

A quick look at the Gravatar instructions show it isn't very complicated to use the server. But in the Rails ecosystem it is even easier: there's a gem for that!

  # Edit your gem file:
  gem 'gravatar_image_tag'

  bundle install

  # in app/views/users/show.html.erb
  <%= gravatar_image_tag '[email protected]' %>      

Also add the user sidebar (section 7.3.3) and augement the stylesheet.

Sign Up Form

Standard web form behavior is to return error messages next to the field that has the bad data in it - without losing any of the other data you have entered into the form. Doing that by hand is tedious. Rails takes the drudgery out of that with form_for and a handful of form helpers like text_field.

# app/views/users/new.html.erb
<h1>Sign up</h1>

<%= form_for(@user) do |f| %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :password %><br />
    <%= f.password_field :password %>
  </div>
  <div class="field">
    <%= f.label :password_confirmation, "Confirmation" %><br />
    <%= f.password_field :password_confirmation %>
  </div>
  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>      
# app/controllers/users_controller.rb
  def new
    @user = User.new  # need this for form_for to act on
    @title = "Sign up"
  end  

Sign Up Form 2

Now let's look at the html generated by form_for and friends:

# Form fields. Note the generated name
<input id="user_name" name="user[name]" - - - />

# And the form definition itself:
<form action="/users" class="new_user" id="new_user" method="post">
<div style="margin:0;padding:0;display:inline">
<input name="authenticity_token" type="hidden"
       value="rB82sI7Qw5J9J1UMILG/VQL411vH5putR+JwlxLScMQ=" />
</div>      

So when we submit the form, where will it end up? Let's check our routes:

$ rake routes
    users GET    /users(.:format)          {:action=>"index",  :controller=>"users"}
          POST   /users(.:format)          {:action=>"create", :controller=>"users"}
 new_user GET    /users/new(.:format)      {:action=>"new",    :controller=>"users"}
edit_user GET    /users/:id/edit(.:format) {:action=>"edit",   :controller=>"users"}
     user GET    /users/:id(.:format)      {:action=>"show",   :controller=>"users"}
          PUT    /users/:id(.:format)      {:action=>"update", :controller=>"users"}
          DELETE /users/:id(.:format)      {:action=>"destroy", :controller=>"users"}      

Create a User

class UsersController < ApplicationController
  def create
    @user = User.new(params[:user])
    if @user.save
      redirect_to @user
    else
      @title = "Sign up"
      render 'new'
    end
  end
end  

Error Handling

Recall that ActiveRecord gives you an array of error messages - everything that is wrong with the user you tried to create: a_user.errors.full_messages So how can we show those to the user?

# make a partial app/views/shared/_error_messages.html.erb
<% if @user.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@user.errors.count, "error") %> 
        prohibited this user from being saved:</h2>
    <p>There were problems with the following fields:</p>
    <ul>
    <% @user.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>      

Ruby goodness: .any?, .each do |msg| end. Rails goodness: pluralize

Flash Notices

When user creation succeeds, the user gets redirected to their user page. But perhaps we would like to set a temporary message on that first page view. For example: "Welcome! You can now start posting!" Rails provides a way to add a temporary message for the very next page view - called the flash.

# To create them:
  # In app/controllers/users_controller.rb
  flash[:success] = "Welcome to the Sample App!"
      
# To display them
  # In app/views/layouts/application.html.erb
  <% flash.each do |key, value| %>
    <div class="flash <%= key %>"><%= value %></div>
  <% end %>      

User Sessions

You can look at being signed in or not as an attribute of the user. But Rails prefers to think of signing in as "creating a new session" and signing out as "destroying a session". So the usual way to implement user sign ins in Rails is to create a Session controller.

  rails generate controller Sessions new      

We will also need actions for create and destroy - but they won't need views, so we'll just add them by hand later. We will need some routes - and will probably want them to have pretty names.

SampleApp::Application.routes.draw do
  resources :users
  resources :sessions, :only => [:new, :create, :destroy]

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

Sign In Form

So to sign in, we'll need a form that is similar to our signup form - except we'll only need email and password. The other main difference is that we don't have a database model for the session (it's data is stored in a session cookie on your browser). So we'll need to use form_for a little differently:

  # instead of 
  form_for(@user)

  # we need to tell form_for the name of the resource and a target url
  form_for(:session, :url => sessions_path)          
# app/views/sessions/new.html.erb
<h1>Sign in</h1>

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

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

Sign In Form 2

So what is the usual logic for form submission?

We saw how to implement that with form_for and an ActiveRecord model (validations, error reporting, etc.). So how do we do that with our session form?

We use our User.authenticate action

  def create
    user = User.authenticate(params[:session][:email],
                             params[:session][:password])
    if user.nil?
      # Create error messages and rerender new
      flash.now[:error] = "Invalid email/password combination."
      @title = "Sign in"
      render 'new'
    else
      # set session cookie and redirect user to his own page
    end
 end      

Session Cookies

The most common way of keeping track of user sessions is to use browser cookies. Rails has machinery for creating encrypted cookies and allowing you to easily store data in and retrieve data from the cookie. We don't want everyone to be able to read that user_id so we are going to encrypt it. We will reuse the salt we created to encrypt each user's password to encrypt their user id before storing it in a cookie named "remember_token".

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

For more details on security threats and how to use Rails to counter them, see the Rails Security Guide.

Session Cookies 2

But where does the code above go? We will need to know if you are signed in or not in our controllers and in our views. Views already load all helper methods - and we can easily load the SessionHelper module in our application controller. So let's put our sign_in code and its auxillary functions in to the SessionsHelper module.

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

  # and a a nice method for checking if the person viewing this page is signed in
  def signed_in?
    !current_user.nil?
  end
end 

class ApplicationController < ActionController::Base
  protect_from_forgery
  include SessionsHelper
end      

Setting the Current User

In the previous step, we created a current_user attribute in the session. Or did we? We haven't created a setter method for current_user. There isn't an attr_write nor an current_user= method. If you try to sign in right now, you will get a stack trace:

NoMethodError in SessionsController#create

undefined method `current_user=' for #<SessionsController:0xb1d8d40>

app/helpers/sessions_helper.rb:5:in `sign_in'
app/controllers/sessions_controller.rb:16:in `create' 

We better create methods for setting and retrieving the current user from the session. NB @current_user is an instance variable of the current session.

  def current_user=(user)
    @current_user = user
  end 

Using the Current User Variable

You might think we could just create a getter that returns @current_user. However, since that is an instance variable, you would get a different instance for every page request. Not very helpful if you are trying to preserve state across multiple page requests.

  def current_user
    @current_user ||= user_from_remember_token
  end

  private

    def user_from_remember_token
      User.authenticate_with_salt(*remember_token)
    end

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

Note a couple of Ruby tricks: ||= and the splat operator *array

Authenticating Users with ID and Salt

class User < ActiveRecord::Base
...
  def self.authenticate(email, submitted_password)
    user = find_by_email(email)
    return nil  if user.nil?
    return user if user.has_password?(submitted_password)
  end

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

The two methods above have the exact same logic - but the second one is written using the ternary operator. If you have seen it before, feel free to use it. Otherwise, it is also OK to use the longer, more explicit form of the first method.

Signing Out

So signing out is the same thing as "destroying" the session. Essentially we need to clear out the remember_token cookie and the @current_user variable. And then take the user ???

class SessionsController < ApplicationController

 def destroy
    sign_out
    redirect_to root_path
  end
end      
module SessionsHelper

  def sign_out
    cookies.delete(:remember_token)
    self.current_user = nil
  end
end      

Signing In When Signing Up

Now that we can sign people in, we should probably do that automatically when someone signs up. So let's modify our User#create action to do that.

class UsersController < ApplicationController

  def create
    @user = User.new(params[:user])
    if @user.save
      sign_in @user
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      @title = "Sign up"
      render 'new'
    end
  end
end      

Tweaking Our Layout

It is customary to have the sign in link change to a sign out link once a user signs in. We had better provide a sign out link to our users - especially as we use permanent cookies for this app. We will probably also want users to be able to get back to their own page at any time while they are signed in.

<header>
  <%= image_tag("logo.png", :alt => "Sample App", :class => "round") %>
  <nav class="round">
    <ul>
      <li><%= link_to "Home", root_path %></li>
      <% if signed_in? %>
          <li><%= link_to "Profile", current_user %></li>
      <% end %>
      <li><%= link_to "Help", help_path %></li>
      <% if signed_in? %>
          <li><%= link_to "Sign out", signout_path, :method => :delete %></li>
      <% else %>
          <li><%= link_to "Sign in", signin_path %></li>
      <% end %>
    </ul>
  </nav>
</header>
      

Editing User Information

But what if my email address changes? Let's add an edit action.

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

The edit form needs to look a lot like the sign up form. Can't we just reuse it? Let's refactor it so we can. First, remove the form contents from new.html.erb and put them in a file called _form.html.erb. And reinclude them as below:

  <%= render :partial => 'form', :locals => { :f => f } %>      

Then you can copy new.html.erb into edit.html.erb and modify as need be.

Now, let's make it possible to get to the edit page by adding a "Settings" link when you are signed in:

      <% if signed_in? %>
          <li><%= link_to "Profile", current_user %></li>
          <li><%= link_to "Settings", edit_user_path(current_user) %></li>
      <% end %>      

Update User Info

By now you are kind of getting the hang of page flow. We'll need an action to receive the form information, validate it, and return errors or redirect back to the profile page. By convention Rails calls this action "update".

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(params[:user])
      flash[:success] = "Profile updated."
      redirect_to @user
    else
      @title = "Edit user"
      render 'edit'
    end
  end      

Protecting pages

Pretty nifty, huh? The update_attributes function is the bee's knees. But at the moment, any and everyone can edit your profile information. Hmmm perhaps we should restrict that so that you can only edit your own profile.

class UsersController < ApplicationController
  before_filter :authenticate, :only => [:edit, :update]
  ...
  private

    def authenticate
      deny_access unless signed_in?
    end
end      
module SessionsHelper
  ...
  def deny_access
    redirect_to signin_path, :notice => "Please sign in to access this page."
  end
end      

Protecting pages 2

The previous code made is so only signed in users can edit profiles - but it still didn't mean you couldn't edit someone else's profile. Let's add another filter for that:

class UsersController < ApplicationController
  before_filter :authenticate, :only => [:edit, :update]
  before_filter :correct_user, :only => [:edit, :update]
  ....
  private

    def authenticate
      deny_access unless signed_in?
    end

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

  def current_user?(user)
    user == current_user
  end

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

And now that we are only allowing the current user to edit, we don't need to look up the user from params[:id], we can just use the current_user. So the edit action changes as follows:

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

def edit
  # correct_user filter now sets @user 
  @title = "Edit user"
end
      

Better Page Flow

OK our edit function is now secure. But it is kind of annoying. If the user gets redirected to the sign in page, she ends up on her profile display page - even if she had been in the middle of trying to edit the profile. That isn't the behavior you expect - you want to go right back to editing, right?

Remember that phrase about "HTTP is a stateless protocol"? So if we want to go "back where we started" then we'll need to store that information explicitly. Fortunately, Rails has a session cookie just for stuff like this.

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

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

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

    def clear_return_to
      session[:return_to] = nil
    end
end      
  # and change the last line of SessionsController#create from
  # redirect_to user
  redirect_back_or user
     

Making It Social

So far we have built the user infrastructure that 90% of web sites need these days. But our "social media" Twitter clone is, so far, pretty anti-social. There isn't any way to see other users of the site. Let's fix that. Let's create a page listing all the users. In Rails/REST convention, that page is called the index page and should be at the "/users" url. We only want other signed in users to see that list, so let's add index to the list of restricted pages.

class UsersController < ApplicationController
  before_filter :authenticate, :only => [:index, :edit, :update]
  ...
  def index
    @title = "All users"
    @users = User.all
  end      
# app/views/users/index.html.erb
<h1>All users</h1>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, :size => 30 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>      
  # and add a link to Users in the navigation - if you are signed in
      <li><%= link_to "Users", users_path %></li<

Rushing On

Section 10.3.3 shows you an extremely convenient Rails extension to easily split the user list (or any other list) onto multiple pages using the will-paginate gem. We are going to skip this, but take a look at the book when you have time.

We are also going to skip section 10.4 on creating administrator accounts and deleting users. But when you build your first Rails project, be sure to go back and read that section carefully because there is important information about how to prevent hackers from giving themselves admin rights to your application. (Hint: it has to do with the infamous "mass-assignment vulnerability" and attr_accessible.)

Finally: Tweets!

rails generate model Micropost content:string user_id:integer      

We are going to want to query microposts out of the database by user and then order them by their created_at timestamp. Our queries will be much faster if we add an index that is based on user_id + the created_at attribute. So before running db:migrate, let's edit our migration file to add that index:

add_index :microposts, [:user_id, :created_at] 
# and run the migration
rake db:migrate 

Mass Assignment

The first thing we want to do for any model is define which fields should be editable through the web interface. Those fields should be added to attr_accessible To prevent congressional scandals, we only want user_id set by our code and not through the web interface.

class Micropost < ActiveRecord::Base
  attr_accessible :content
end   
$ rails console
Loading development environment (Rails 3.0.10)
> u = Micropost.new(:content => "foo bar", :user_id => 1)
 => #<Micropost id: nil, content: "foo bar", user_id: nil, created_at: nil, updated_at: nil>
> u.user_id
 => nil
> u.user_id = 1
 => 1
> u
 => #<Micropost id: nil, content: "foo bar", user_id: 1, created_at: nil, updated_at: nil>
> u.save
 => true
> u
 => #<Micropost id: 1, content: "foo bar", user_id: 1, created_at: "2011-09-24 15:49:27", updated_at: "2011-09-24 15:49:27">  

Associating Tweets With Users

One of the most compelling reasons to use a framework like ActiveRecord for dealing with your database is that ActiveRecord makes it so easy to get from users to their microposts - and back.

class User < ActiveRecord::Base
  attr_accessor :password
  attr_accessible :name, :email, :password, :password_confirmation

  has_many :microposts
end

class Micropost < ActiveRecord::Base
  attr_accessible :content
  belongs_to :user
end      

Using Associations

Be adding the belongs_to and has_many lines to the Micropost and User classes, you have told Rails to use Ruby's metaprogramming abilities to add a bunch of methods to each class:

> cnk = User.find(1)
 => #<User id: 1, name: "Cynthia Kiser", email: "[email protected]", ..>

> cnk.microposts
 => []

> cnk.microposts.create(:content => "My first post")
 => #<Micropost id: 2, content: "My first post", user_id: 1, ...">

> cnk.microposts.create(:content => "My second post")
 => #<Micropost id: 3, content: "My second post", user_id: 1, ...">

> cnk.microposts
 => [#<Micropost id: 2, content: "My first post", user_id: 1, ...">, 
#<Micropost id: 3, content: "My second post", user_id: 1, ...">]

> m = Micropost.first
 => #<Micropost id: 2, content: "My first post", user_id: 1,...">

> m.user
 => #<User id: 1, name: "Cynthia Kiser", email: "[email protected]", ...>
 

Association Refinements

We will always want to display microposts in reverse chronlogicall order, so let's set a default scope so any query just does that:

  default_scope :order => 'microposts.created_at DESC'
      

And if we delete a user, we want to delete their posts too.

 has_many :microposts, :dependent => :destroy      

All microposts need content and a user_id. And we also want to use Rails validations to restrict the length of the microposts to < 140 characters.

  validates :content, :presence => true, :length => { :maximum => 140 }
  validates :user_id, :presence => true      

Showing Microposts

OK now, if we had some microposts, we would want to display them on the user's page, right? So let's do that.

Adding the display before we have anything to display may seem odd. BUT it helps us remember to do something graceful if the user doesn't have any microposts yet - such as when she first signs up.

      <% unless @user.microposts.empty? %>
        <table class="microposts" summary="User microposts">
          <%= render @microposts %>
        </table>
      <% end %>