
- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •Models, Views, and Controllers
- •Installing Rails
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Unix/Linux
- •Rails and Databases
- •Keeping Up-to-Date
- •Rails and ISPs
- •Creating a New Application
- •Hello, Rails!
- •Linking Pages Together
- •What We Just Did
- •Building an Application
- •The Depot Application
- •Incremental Development
- •What Depot Does
- •Task A: Product Maintenance
- •Iteration A1: Get Something Running
- •Iteration A2: Add a Missing Column
- •Iteration A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B2: Add Page Decorations
- •Task C: Cart Creation
- •Sessions
- •More Tables, More Models
- •Iteration C1: Creating a Cart
- •Iteration C3: Finishing the Cart
- •Task D: Checkout!
- •Iteration D2: Show Cart Contents on Checkout
- •Task E: Shipping
- •Iteration E1: Basic Shipping
- •Task F: Administrivia
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Finishing Up
- •More Icing on the Cake
- •Task T: Testing
- •Tests Baked Right In
- •Testing Models
- •Testing Controllers
- •Using Mock Objects
- •Test-Driven Development
- •Running Tests with Rake
- •Performance Testing
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Active Support
- •Logging in Rails
- •Debugging Hints
- •Active Record Basics
- •Tables and Classes
- •Primary Keys and IDs
- •Connecting to the Database
- •Relationships between Tables
- •Transactions
- •More Active Record
- •Acts As
- •Aggregation
- •Single Table Inheritance
- •Validation
- •Callbacks
- •Advanced Attributes
- •Miscellany
- •Action Controller and Rails
- •Context and Dependencies
- •The Basics
- •Routing Requests
- •Action Methods
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Builder templates
- •RHTML Templates
- •Helpers
- •Formatting Helpers
- •Linking to Other Pages and Resources
- •Pagination
- •Form Helpers
- •Layouts and Components
- •Adding New Templating Systems
- •Introducing AJAX
- •The Rails Way
- •Advanced Techniques
- •Action Mailer
- •Sending E-mail
- •Receiving E-mail
- •Testing E-mail
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Securing Your Rails Application
- •SQL Injection
- •Cross-Site Scripting (CSS/XSS)
- •Avoid Session Fixation Attacks
- •Creating Records Directly from Form Parameters
- •Knowing That It Works
- •Deployment and Scaling
- •Picking a Production Platform
- •A Trinity of Environments
- •Iterating in the Wild
- •Maintenance
- •Finding and Dealing with Bottlenecks
- •Case Studies: Rails Running Daily
- •Appendices
- •Introduction to Ruby
- •Ruby Names
- •Regular Expressions
- •Source Code
- •Cross-Reference of Code Samples
- •Resources
- •Index

Chapter 11
Task F: Administrivia
We have a happy customer—in a very short time we’ve jointly put together a basic shopping cart that she can start showing to customers. There’s just one more change that she’d like to see. Right now, anyone can access the administrative functions. She’d like us to add a basic user administration system that would force you to log in to get into the administration parts of the site.
We’re happy to do that, as it gives us a chance to play with callback hooks and filters, and it lets us tidy up the application somewhat.
Chatting with our customer, it seems as if we don’t need a particularly sophisticated security system for our application. We just need to recognize a number of people based on user names and passwords. Once recognized, these folks can use all of the administration functions.
11.1 Iteration F1: Adding Users
Let’s start by creating a simple database table to hold the user names and hashed passwords for our administrators.1
File 57 |
create table users ( |
|
|
|
id |
int |
not null auto_increment, |
|
name |
varchar(100) |
not null, |
|
hashed_password char(40) |
null, |
|
|
primary key (id) |
|
|
|
); |
|
|
We’ll create the Rails model too.
1Rather than store passwords in plain text, we’ll feed them through an SHA1 digest, resulting in a 160-bit hash. We check a user’s password by digesting the value they give us and comparing that hashed value with the one in the database.
Prepared exclusively for Rida Al Barazi

ITERATION F1: ADDING USERS 119
depot> ruby script/generate model User exists app/models/
exists test/unit/ exists test/fixtures/
create app/models/user.rb create test/unit/user_test.rb create test/fixtures/users.yml
Now we need some way to create the users in this table. In fact, it’s likely that we’ll be adding a number of functions related to users: login, list, delete, add, and so on. Let’s keep things tidy by putting them into their own controller. At this point, we could invoke the Rails scaffolding generator that we used when we work on product maintenance, but this time let’s do it by hand.2 That way, we’ll get to try out some new techniques. So, we’ll generate our controller (Login) along with a method for each of the actions we want.
depot> ruby script/generate controller Login add_user login logout \
|
delete_user list_users |
exists |
app/controllers/ |
exists |
app/helpers/ |
create |
app/views/login |
exists |
test/functional/ |
create |
app/controllers/login_controller.rb |
create test/functional/login_controller_test.rb |
|
create |
app/helpers/login_helper.rb |
create |
app/views/login/login.rhtml |
create |
app/views/login/add_user.rhtml |
create |
app/views/login/delete_user.rhtml |
create |
app/views/login/list_users.rhtml |
We know how to create new rows in a database table; we create an action, put a form into a view, and invoke the model to save data away. But to make this chapter just a tad more interesting, let’s create users using a slightly different style in the controller.
In the automatically generated scaffold code that we used to maintain the products table, the edit action set up a form to edit product data. When that form was completed by the user, it was routed back to a separate save action in the controller. Two separate methods cooperated to get the job done.
In contrast, our user creation code will use just one action, add_user( ). Inside this method we’ll detect whether we’re being called to display the initial (empty) form or whether we’re being called to save away the data in a completed form. We’ll do this by looking at the HTTP method of the
2In fact, we probably wouldn’t use scaffolds at all. You can download Rails code generators which will write user management code for you. Search the Rails wiki (wiki.rubyonrails.com) for login generator. The Salted Hash version is the most secure from brute-force attacks.
Prepared exclusively for Rida Al Barazi
Report erratum

|
ITERATION F1: ADDING USERS |
120 |
|
incoming request. If it has no associated data, it will come in as a GET |
|
|
request. If instead it contains form data, we’ll see a POST. Inside a Rails |
|
|
controller, the request information is available in the attribute request. We |
|
|
can check the request type using the methods get?( ) and post?( ). Here’s |
|
|
the code for the add_user( ) action in the file login_controller.rb. (Note that |
|
|
we added the admin layout to this new controller—let’s make the screen |
|
|
layouts consistent across all administration functions.) |
|
File 52 |
class LoginController < ApplicationController |
|
|
layout "admin" |
|
|
def add_user |
|
|
if request.get? |
|
|
@user = User.new |
|
|
else |
|
|
@user = User.new(params[:user]) |
|
|
if @user.save |
|
|
redirect_to_index("User #{@user.name} created") |
|
|
end |
|
|
end |
|
|
end |
|
|
# . . . |
|
|
If the incoming request is a GET, the add_user( ) method knows that there |
|
|
is no existing form data, so it creates a new User object for the view to use. |
|
|
If the request is not a GET, the method assumes that POST data is present. |
|
|
It loads up a User object with data from the form and attempts to save it |
|
|
away. If the save is successful, it redirects to the index page; otherwise it |
|
|
displays its own view again, allowing the user to correct any errors. |
|
|
To get this action to do anything useful, we’ll need to create a view for |
|
|
it. This is the template add_user.rhtml in app/views/login. Note that the |
|
|
form_tag needs no parameters, as it defaults to submitting the form back |
|
|
to the action and controller that rendered the template. |
|
File 55 |
<% @page_title = "Add a User" -%> |
|
|
<%= error_messages_for 'user' %> |
|
<%= form_tag %>
<table> <tr>
<td>User name:</td>
<td><%= text_field("user", "name") %></td>
</tr> <tr>
<td>Password:</td>
<td><%= password_field("user", "password") %></td>
</tr>
<tr> <td></td>
<td><input type="submit" value=" ADD USER " /></td>
</tr> </table>
<%= end_form_tag %>
Prepared exclusively for Rida Al Barazi
Report erratum

ITERATION F1: ADDING USERS 121
|
What’s less straightforward is our user model. In the database, the user’s |
|
password is stored as a 40-character hashed string, but on the form the |
|
user types it in plain text. The user model needs to have a split personal- |
|
ity, maintaining the plain-text password when dealing with form data but |
|
switching to deal with a hashed password when writing to the database. |
|
Because the User class is an Active Record model, it knows about the |
|
columns in the users table—it will have a hashed_password attribute auto- |
|
matically. But there’s no plain-text password in the database, so we’ll use |
|
Ruby’s attr_accessor to create a read/write attribute in the model. |
File 54 |
class User < ActiveRecord::Base |
|
attr_accessor :password |
|
We need to ensure that the hashed password gets set from the value in the |
|
plain-text attribute before the model data gets written to the database. We |
|
can use the hook facility built into Active Record to do just that. |
|
Active Record defines a large number of callback hooks that are invoked |
|
at various points in the life of a model object. Callbacks run, for example, |
|
before a model is validated, before a row is saved, after a new row has been |
|
created, and so on. In our case, we can use the before and after creation |
|
callbacks to manage the password. |
|
Before the user row is saved, we use the before_create( ) hook to take a |
|
plain-text password and apply the SHA1 hash function to it, storing the |
|
result in the hashed_password attribute. That way, the hashed_password |
|
column in the database will be set to the hashed value of the plain-text |
|
password just before the model is written out. |
|
After the row is saved, we use the after_create( ) hook to clear out the plain- |
|
text password field. This is because the user object will eventually get |
|
stored in session data, and we don’t want these passwords to be lying |
|
around on disk for folks to see. |
|
There are a number of ways of defining hook methods. Here, we’ll simply |
|
define methods with the same name as the callbacks (before_create( ) and |
|
after_create( )). Later, on page 126, we’ll see how we can do it declaratively. |
|
Here’s the code for this password manipulation. |
File 54 |
require "digest/sha1" |
|
class User < ActiveRecord::Base |
|
attr_accessor :password |
|
attr_accessible :name, :password |
|
def before_create |
|
self.hashed_password = User.hash_password(self.password) |
|
end |
attr_accessor
→ page 472
Prepared exclusively for Rida Al Barazi
Report erratum

|
ITERATION F1: ADDING USERS |
122 |
|
def after_create |
|
|
@password = nil |
|
|
end |
|
|
private |
|
|
def self.hash_password(password) |
|
|
Digest::SHA1.hexdigest(password) |
|
|
end |
|
|
end |
|
|
Add a couple of validations, and the work on the user model is done (for |
|
|
now). |
|
File 54 |
class User < ActiveRecord::Base |
|
attr_accessor :password
attr_accessible :name, :password
validates_uniqueness_of :name validates_presence_of :name, :password
The add_user( ) method in the login controller calls the redirect_to_index( ) method. We’d previously defined this in the store controller on page 91, so it isn’t accessible in the login controller. To make the redirection method accessible across multiple controllers we need to move it out of the store controller and into the file application.rb in the app/controllers directory. This file defines class ApplicationController, which is the parent of all the controller classes in our application. Methods defined here are available in all these controllers.
File 51 |
class ApplicationController < ActionController::Base |
|
model :cart |
|
model :line_item |
|
private |
|
def redirect_to_index(msg = nil) |
|
flash[:notice] = msg if msg |
redirect_to(:action => 'index') end
end
That’s it: we can now add users to our database. Let’s try it. Navigate to http://localhost:3000/login/add_user, and you should see this stunning example of page design.
Prepared exclusively for Rida Al Barazi
Report erratum