- •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 6
Task A: Product Maintenance
Our first development task is to create the web interface that lets us maintain our product information—create new products, edit existing products, delete unwanted ones, and so on. We’ll develop this application in small iterations, where small means “measured in minutes.” Let’s get started....
6.1 Iteration A1: Get Something Running
Perhaps surprisingly, we should get the first iteration of this working in almost no time. We’ll start off by creating a new Rails application. This is where we’ll be doing all our work. Next, we’ll create a database to hold our information (in fact we’ll create three databases). Once that groundwork is in place, we’ll
•create the table to hold the product information,
•configure our Rails application to point to our database(s), and
•have Rails generate the initial version of our product maintenance application for us.
Create a Rails Application
Back on page 25 we saw how to create a new Rails application. Go to a command prompt, and type rails followed by the name of our project. In this case, our project is called depot, so type
work> rails depot
We see a bunch of output scroll by. When it has finished, we find that a new directory, depot, has been created. That’s where we’ll be doing our work.
Prepared exclusively for Rida Al Barazi
|
|
|
ITERATION A1: GET SOMETHING RUNNING |
50 |
|
work> cd depot |
|
|
|
|
|
work> ls |
|
|
|
|
|
CHANGELOG |
app |
db |
log |
test |
|
README |
components |
doc |
public |
vendor |
|
Rakefile |
config |
lib |
script |
|
|
Create the Databases
For this application, we’ll use the open-source MySQL database server (which you’ll need too if you’re following along with the code). For reasons that will become clear later, we’re actually going to create three databases.
•depot_development will be our development database. All of our programming work will be done here.
•depot_test is a test database. It is considered to be transient, so it’s perfectly acceptable for us to empty it out to give our tests a fresh place to start each time they run.
•depot_production is the production database. Our application will use this when we put it online.
We’ll use the mysql command-line client to create our databases, but if you’re more comfortable with tools such as phpmyadmin or CocoaMySQL, go for it. (In the session that follows, we’ve stripped out MySQL’s somewhat useless responses to each command.)
depot> mysql -u root -p
Enter password: *******
Welcome to the MySQL monitor. Commands end with ; or \g.
mysql> create database depot_development; mysql> create database depot_test;
mysql> create database depot_production;
mysql> grant all on depot_development.* to 'dave'@'localhost'; mysql> grant all on depot_test.* to 'dave'@'localhost';
mysql> grant all on depot_production.* to 'prod'@'localhost' identified by 'wibble'; mysql> exit
Create the Products Table
Back in Figure 5.3, on page 47, we sketched out the basic content of the products table. Now let’s turn that into reality. Here’s the Data Definition Language (DDL) for creating the products table in MySQL.
File 22 |
drop table if |
exists products; |
|
|
create table products ( |
|
|
|
id |
int |
not null auto_increment, |
|
title |
varchar(100) |
not null, |
|
description |
text |
not null, |
|
image_url |
varchar(200) |
not null, |
|
price |
decimal(10,2) |
not null, |
primary key (id) );
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION A1: GET SOMETHING RUNNING |
51 |
Our table includes the product title, description, image, and price, just as we sketched out. We’ve also added something new: a column called id. This is used to give each row in the table a unique key, allowing other tables to reference products. But there’s more to this id column. By default, Rails assumes that every table it handles has as its primary key an integer column called id.1 Internally, Rails uses the value in this column to keep track of the data it has loaded from the database and to link between data in different tables. You can override this naming system, but unless you’re using Rails to work with legacy schemas that you can’t change, we recommend you just stick with using the name id.
It’s all very well coming up with the DDL for the products table, but where should we store it? I’m a strong believer in keeping the DDL for my application databases under version control, so I always create it in a flat file. For a Rails application, I call the file create.sql and put it in my application’s db subdirectory. This lets me use the mysql client to execute the DDL and create the table in my development database. Again, you’re free to do this using GUI or web-based tools if you prefer.
depot> mysql depot_development <db/create.sql
Configure the Application
In many simple scripting-language web applications, the information on how to connect to the database is embedded directly into the code—you might find a call to some connect( ) method, passing in host and database names, along with a user name and password. This is dangerous, because password information sits in a file in a web-accessible directory. A small server configuration error could expose your password to the world.
The approach of embedding connection information into code is also inflexible. One minute you might be using the development database as you hack away. Next you might need to run the same code against the test database. Eventually, you’ll want to deploy it into production. Every time you switch target databases, you have to edit the connection call. There’s a rule of programming that says you’ll mistype the password only when switching the application into production.
Smart developers keep the connection information out of the code. Sometimes you might want to use some kind of repository to store it all (Java developers often use JNDI to look up connection parameters). That’s a bit
1Note that the case is significant. If you use a nannyish GUI tool that insists on changing the column name to Id, you might have problems.
Prepared exclusively for Rida Al Barazi
Report erratum
|
|
|
|
ITERATION A1: GET SOMETHING RUNNING |
52 |
||
development: |
|
|
|
|
development: |
|
|
adapter: |
mysql |
|
|
|
adapter: |
mysql |
|
database: |
rails_development |
|
|
|
database: |
depot_development |
|
host: |
localhost |
|
|
|
host: |
localhost |
|
username: root |
|
|
|
username: <blank> |
|
||
password: |
|
|
|
|
password: |
|
|
test: |
|
|
|
|
test: |
|
|
adapter: |
mysql |
|
|
|
adapter: |
mysql |
|
database: |
rails_test |
|
|
|
database: |
depot_test |
|
host: |
localhost |
|
Edit the file |
host: |
localhost |
|
|
username: root |
|
|
|
username: <blank> |
|
||
config/database.yml |
|
||||||
password: |
|
|
|
|
password: |
|
|
production: |
|
|
|
|
production: |
|
|
adapter: |
mysql |
|
|
|
adapter: |
mysql |
|
database: |
rails_production |
|
|
|
database: |
depot_production |
|
host: |
localhost |
|
|
|
host: |
localhost |
|
username: root |
username: prod |
password: |
password: wibble |
Original File |
New File |
Figure 6.1: Configure thedatabase.ymlFile
heavy for the average web application that we’ll write, so Rails simply uses a flat file. You’ll find it in config/database.yml.2
As Figure 6.1 shows, database.yml contains three sections, one each for the development, test, and production databases. Using your favorite editor, change the fields in each to match the databases we created. Note that in the diagram we’ve left the username fields blank for the development and test environments in the new database.yml file. This is convenient, as it means that different developers will each use their own usernames when connecting. However, we’ve had reports that with some combinations of MySQL, database drivers, and operating systems, leaving these fields blank makes Rails attempt to connect to the database as the root user. Should you get an error such as Access denied for user ’root’@’localhost.localdomain’, put an explicit username in these two fields.
Create the Maintenance Application
OK. All the ground work has been done. We set up our Depot application as a Rails project. We’ve created the databases and the products table. And
2The .yml part of the name stands for YAML, or YAML Ain’t a Markup Language. It’s a simple way of storing structured information in flat files (and it isn’t XML). Recent Ruby releases include built-in YAML support.
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION A1: GET SOMETHING RUNNING |
53 |
we configured our application to be able to connect to the databases. Time to write the maintenance app.
depot> ruby script/generate scaffold Product Admin dependency model
exists app/models/ exists test/unit/ exists test/fixtures/
create app/models/product.rb create test/unit/product_test.rb
::
create app/views/admin/show.rhtml create app/views/admin/new.rhtml create app/views/admin/edit.rhtml create app/views/admin/_form.rhtml
That wasn’t hard now, was it?3,4
That single command has written a basic maintenance application. The Product parameter told the command the name of the model we want, and the Admin parameter specifies the name of the controller. Before we worry about just what happened behind the scenes here, let’s try our shiny new application. First, we’ll start a local WEBrick-based web server, supplied with Rails.
depot> ruby script/server
=> Rails application started on http://0.0.0.0:3000 [2005-02-08 12:08:40] INFO WEBrick 1.3.1
[2005-02-08 12:08:40] INFO ruby 1.8.2 (2004-12-30) [powerpc-darwin7.7.0] [2005-02-08 12:08:40] INFO WEBrick::HTTPServer#start: pid=20261 port=3000
Just as it did with our demo application in Chapter 4, Instant Gratification, this command starts a web server on our local host, port 3000.5 Let’s connect to it. Remember, the URL we give to our browser contains both the port number (3000) and the name of the controller in lowercase (admin).
3Unless, perhaps, you’re running OS X 10.4. It seems as if Tiger has broken Ruby’s standard MySQL library. If you see the error Before updating scaffolding from new DB schema, try creating a table for your model (Product), it may well be because Ruby (and hence Rails) can’t get to the database. To fix Apple’s bad install, you’re going to need to reinstall Ruby’s MySQL library, which means going back to on page 21, running the script to repair the Ruby installation, and then reinstalling the mysql gem.
4Some readers also report getting the error Client does not support authentication protocol requested by server; consider upgrading MySQL client. This incompatibility between the version of MySQL installed and the libraries used to access it can be resolved by following the instructions at http://dev.mysql.com/doc/mysql/en/old-client.html and issuing a MySQL command such as set password for ’some_user’@’some_host’ = OLD_PASSWORD(’newpwd’);.
5You might get an error saying Address already in use when you try to run WEBrick. That simply means that you already have a Rails WEBrick server running on your machine. If you’ve been following along with the examples in the book, that might well be the Hello World! application from Chapter 4. Find its console, and kill the server using control-C.
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION A1: GET SOMETHING RUNNING |
54 |
Port: 3000 Controller: admin
That’s pretty boring. It’s showing us a list of products, and there aren’t any products. Let’s remedy that. Click the New product link, and a form should appear. Figure 6.2, on the following page shows the form after it is filled in. Click the Create button, and you should see the new product in the list (Figure 6.3, on the next page). Perhaps it isn’t the prettiest interface, but it works, and we can show it to our client for approval. They can play with the other links (showing details, editing existing products, as shown in Figure 6.4, on page 56). We explain to them that this is only a first step—we know it’s rough, but we wanted to get their feedback early. (And 25 minutes into the start of coding probably counts as early in anyone’s book.)
Rails Scaffolds
We covered a lot of ground in a very short initial implementation, so let’s take a minute to look at that last step in a bit more detail.
A Rails scaffold is an autogenerated framework for manipulating a model. When we run the generator, we tell it that we want a scaffold for a particular model (which it creates) and that we want to access it through a given controller (which it also creates).
In Rails, a model is automatically mapped to a database table whose name is the plural form of the model’s class. In our case, we asked for a model called Product, so Rails associated it with the table called products. And how did it find that table? We told it where to look when we set up the development entry in config/database.yml. When we started the application, the model examined the table in the database, worked out what columns it had, and created mappings between the database data and Ruby objects.
name mapping
→ page 180
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION A1: GET SOMETHING RUNNING |
55 |
Figure 6.2: Adding a New Product
Figure 6.3: We Just Added Our First Product
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION A1: GET SOMETHING RUNNING |
56 |
Figure 6.4: Showing Details and Editing
That’s why the New products form came up already knowing about the title, description, image, and price fields—because they are in the database table, they are added to the model. The form generator used by the scaffold can ask the model for information on these fields and uses what it discovers to create an appropriate HTML form.
Controllers handle incoming requests from the browser. A single application can have multiple controllers. For our Depot application, it’s likely that we’ll end up with two of them, one handling the seller’s administration of the site and the other handling the buyer’s experience. We created the product maintenance scaffolding in the Admin controller, which is why the URL that accesses it has admin at the start of its path.
The utility that generates a Rails scaffold populates your application’s directory tree with working Ruby code. If you examine it, you’ll find that what you have is the bare bones of a full application—the Ruby code has been placed inline; it’s all in the source, rather than simply being a single call into some standard library. This is good news for us, because it
Prepared exclusively for Rida Al Barazi
Report erratum