
- •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 9
Task D: Checkout!
Let’s take stock. So far, we’ve put together a basic product administration system, we’ve implemented a catalog, and we have a pretty spiffy-looking shopping cart. So now we need to let the buyer actually purchase the contents of that cart. Let’s go ahead and implement the checkout function.
We’re not going to go overboard here. For now, all we’ll do is capture the customer’s contact details and payment option. Using these we’ll construct an order in the database. Along the way we’ll be looking a bit more at models, validation, form handling, and components.
Joe Asks. . .
Where’s the Credit-Card Processing?
At this point, our tutorial application is going to diverge slightly from reality. In the real world, we’d probably want our application to handle the commercial side of checkout. We might even want to integrate creditcard processing (possibly using the Payment module). However, integrating with backend payment processing systems requires a fair amount of paperwork and jumping through hoops. And this would distract from looking at Rails, so we’re going to punt on this particular detail.
http://rubyforge.org/projects/payment
Prepared exclusively for Rida Al Barazi

ITERATION D1: CAPTURING AN ORDER 96
9.1 Iteration D1: Capturing an Order
An order is a set of line items, along with details of the purchase transaction. We already have the line items—we defined them when we created the shopping cart in the previous chapter. We don’t yet have a table to contain orders. Based on the diagram on page 47, combined with a brief chat with our customer, we can create the orders table.
File 37 |
create table orders ( |
|
|
|
id |
int |
not null auto_increment, |
|
name |
varchar(100) |
not null, |
|
varchar(255) |
not null, |
|
|
address |
text |
not null, |
|
pay_type |
char(10) |
not null, |
primary key (id) );
We know that when we create a new order, it will be associated with one or more line items. In database terms, this means that we’ll need to add a foreign key reference from the line_items table to the orders table, so we’ll take this opportunity to update the DDL for line items too. (Have a look at the listing of create.sql on page 487 to see how the drop table statements should be added.)
File 37 |
create table |
line_items ( |
|
|
id |
int |
not null auto_increment, |
|
product_id |
int |
not null, |
|
order_id |
int |
not null, |
|
quantity |
int |
not null default 0, |
|
unit_price |
decimal(10,2) |
not null, |
|
constraint |
fk_items_product |
foreign key (product_id) references products(id), |
|
constraint |
fk_items_order |
foreign key (order_id) references orders(id), |
primary key (id) );
|
Remember to update the schema (which will empty your database of any |
|
data it contains) and create the Order model using the Rails generator. We |
|
don’t regenerate the model for line items, as the one that’s there is fine. |
|
depot> mysql depot_development <db/create.sql |
|
depot> ruby script/generate model Order |
|
That told the database about the foreign keys. This is a good thing, as |
|
many databases will check foreign key constraints, keeping our code hon- |
|
est. But we also need to tell Rails that an order has many line items and |
|
that a line item belongs to an order. First, we open up the newly created |
|
order.rb file in app/models and add a call to has_many( ). |
File 32 |
class Order < ActiveRecord::Base |
|
has_many :line_items |
|
end |
Prepared exclusively for Rida Al Barazi
Report erratum

ITERATION D1: CAPTURING AN ORDER |
97 |
|
Next, we’ll specify a link in the opposite direction, adding a call to the |
|
method belongs_to( ) in the line_item.rb file. (Remember that a line item was |
|
already declared to belong to a product when we set up the cart.) |
File 31 |
class LineItem < ActiveRecord::Base |
|
belongs_to :product |
|
belongs_to :order |
|
# . . . |
|
We’ll need an action to orchestrate capturing order details. In the previous |
|
chapter we set up a link in the cart view to an action called checkout, so |
|
now we have to implement a checkout( ) method in the store controller. |
File 30 |
def checkout |
@cart = find_cart |
|
@items = @cart.items |
empty? |
if @items.empty? |
→ page 479 |
redirect_to_index("There's nothing in your cart!") |
|
else |
|
@order = Order.new |
|
end |
|
end |
|
Notice how we first check to make sure that there’s something in the cart. |
|
This prevents people from navigating directly to the checkout option and |
|
creating empty orders. |
|
Assuming we have a valid cart, we create a new Order object for the view to |
|
fill in. Note that this order isn’t yet saved in the database—it’s just used |
|
by the view to populate the checkout form. |
|
The checkout view will be in the file checkout.rhtml in the app/views/store |
|
directory. Let’s build something simple that will show us how to marry |
|
form data to Rails model objects. Then we’ll add validation and error han- |
|
dling. As always with Rails, it’s easiest to start with the basics and iterate |
|
towards nirvana (the state of being, not the band). |
|
Rails and Forms
Rails has great support for getting data out of relational databases and into Ruby objects. So you’d expect to find that it has corresponding support for transferring that data back and forth between those model objects and users on browsers.
We’ve already seen one example of this. When we created our product administration controller, we used the scaffold generator to create a form that captures all the data for a new product. If you look at the code for that view (in app/views/admin/new.rhtml), you’ll see the following.
Prepared exclusively for Rida Al Barazi
Report erratum

|
|
ITERATION D1: CAPTURING AN ORDER |
98 |
File 34 |
<h1>New product</h1> |
|
|
|
<%= start_form_tag :action => 'create' %> |
|
|
|
<%= render_partial "form" %> |
|
|
|
<%= submit_tag "Create" %> |
|
|
|
<%= end_form_tag %> |
|
|
|
<%= link_to 'Back', :action => 'list' %> |
|
|
|
This references a subform using render_partial('form').1 That subform, in |
|
|
|
the file _form.rhtml, captures the information about a product. |
|
|
File 33 |
<%= error_messages_for 'product' |
%> |
|
|
<!--[form:product]--> |
|
|
|
<p><label for="product_title">Title</label><br/> |
|
|
|
<%= text_field 'product', 'title' |
%></p> |
|
|
<p><label for="product_description">Description</label><br/> |
|
|
|
<%= text_area 'product', 'description', :rows => 5 %></p> |
|
|
|
<p><label for="product_image_url">Image url</label><br/> |
|
|
|
<%= text_field 'product', 'image_url' %></p> |
|
|
|
<p><label for="product_price">Price</label><br/> |
|
|
|
<%= text_field 'product', 'price' |
%></p> |
|
<p><label for="product_date_available">Date available</label><br/> <%= datetime_select 'product', 'date_available' %></p> <!--[eoform:product]-->
We could use the scaffold generator to create a form for the orders table too, but the Rails-generated form is not all that pretty. We’d like to produce something nicer. Let’s dig further into all those methods in the autogenerated form before creating the data entry form for ourselves.
Rails has model-aware helper methods for all the standard HTML input tags. For example, say we need to create an HTML <input> tag to allow the buyer to enter their name. In Rails, we could write something like the following in the view.
<%= text_field("order", "name", :size => 40 ) %>
Here, text_field( ) will create an HTML <input> tag with type="text". The neat thing is that it would populate that field with the contents of the name field in the @order model. What’s more, when the end user eventually submits the form, the model will be able to capture the new value of this field from the browser’s response and store it, ready to be written to the database as required.
There are a number of these form helper methods (we’ll look at them in more detail starting on page 332). In addition to text_field( ), we’ll be using
1render_partial( ) is a deprecated form of render(:partial=>...). The scaffold generators had not been updated to create code using the newer form at the time this book was written.
Prepared exclusively for Rida Al Barazi
Report erratum

|
ITERATION D1: CAPTURING AN ORDER |
99 |
|
text_area( ) to capture the buyer’s address and select( ) to create a selection |
|
|
list for the payment options. |
|
|
Of course, for Rails to get a response from the browser, we need to link |
|
|
the form to a Rails action. We could do that by specifying a link to our |
|
|
application, controller, and action in the action= attribute of a <form> tag, |
|
|
but it’s easier to use form_tag( ), another Rails helper method that does the |
|
|
work for us. |
|
|
So, with all that background out of the way, we’re ready to create the view |
|
|
to capture the order information. Here’s our first attempt at the check- |
|
|
out.rhtml file in app/views/store directory. |
|
File 36 |
<% @page_title = "Checkout" -%> |
|
|
<%= start_form_tag(:action => "save_order") %> |
|
|
<table> |
|
<tr> <td>Name:</td>
<td><%= text_field("order", "name", "size" => 40 ) %></td>
</tr> <tr>
<td>EMail:</td>
<td><%= text_field("order", "email", "size" => 40 ) %></td>
</tr>
<tr valign="top"> <td>Address:</td>
<td><%= text_area("order", "address", "cols" => 40, "rows" => 5) %></td>
</tr>
<tr>
<td>Pay using:</td> <td><%=
options = [["Select a payment option", ""]] + Order::PAYMENT_TYPES select("order", "pay_type", options)
%></td> </tr> <tr>
<td></td>
<td><%= submit_tag(" CHECKOUT ") %></td>
</tr> </table>
<%= end_form_tag %>
The only tricky thing in there is the code associated with the selection list. We’ve assumed that the list of available payment options is an attribute of the Order model—it will be an array of arrays in the model file. The first element of each subarray is the string to be displayed as option in the selection and the second value gets stored in the database.2 We’d better define the option array in the model order.rb before we forget.
2If we anticipate that other non-Rails applications will update the orders table, we might want to move the list of payment types into a separate lookup table and make the orders column a foreign key referencing that new table. Rails provides good support for generating selection lists in this context too.
Prepared exclusively for Rida Al Barazi
Report erratum

ITERATION D1: CAPTURING AN ORDER 100
Figure 9.1: Our First Checkout Page
File 32 |
PAYMENT_TYPES = [ |
|
|
||
|
[ "Check", |
|
"check" ], |
||
|
[ |
"Credit Card", |
"cc" |
], |
|
|
[ |
"Purchase Order", "po" |
] |
||
|
].freeze |
# freeze to make this array constant |
If there’s no current selection in the model, we’d like to display some prompt text in the browser field. We do this by merging a new option at the start of the selection options returned by the model. This new option has an appropriate display string and a blank value.
So, with all that in place, we can fire up our trusty browser. Add some items to the cart, and click the checkout link. You’ll see a shiny new checkout page like the one in Figure 9.1 .
Looking good! Of course, if you click the Checkout button, you’ll be greeted with
Unknown action
No action responded to save_order
So let’s get on and implement the save_order( ) action in our store controller.
Prepared exclusively for Rida Al Barazi
Report erratum

|
|
ITERATION D1: CAPTURING AN ORDER |
101 |
|
This method has to |
|
|
|
1. Capture the values from the form to populate a new Order model |
|
|
|
|
object. |
|
|
2. Add the line items from our cart to that order. |
|
|
|
3. Validate and save the order. If this fails, display the appropriate mes- |
|
|
|
|
sages and let the user correct any problems. |
|
|
4. Once the order is successfully saved, redisplay the catalog page, |
|
|
|
|
including a message confirming that the order has been placed. |
|
|
The method ends up looking something like this. |
|
|
File 30 |
Line 1 |
def save_order |
|
|
- |
@cart = find_cart |
|
|
- |
@order = Order.new(params[:order]) |
|
|
- |
@order.line_items << @cart.items |
|
|
5 |
if @order.save |
|
|
- |
@cart.empty! |
|
|
- |
redirect_to_index('Thank you for your order.') |
|
|
- |
else |
|
|
- |
render(:action => 'checkout') |
|
10 end
-end
On line 3, we create a new Order object and initialize it from the form data. In this case we want all the form data related to order objects, so we select the :order hash from the parameters (we’ll talk about how forms are linked to models on page 341). The next line adds into this order the line items that are already stored in the cart—the session data is still there throughout this latest action. Notice that we didn’t have to do anything special with the various foreign key fields, such as setting the order_id column in the line item rows to reference the newly created order row. Rails does that knitting for us using the has_many( ) and belongs_to( ) declarations we added to the Order and LineItem models.
Next, on line 5, we tell the order object to save itself (and its children, the line items) to the database. Along the way, the order object will perform validation (but we’ll get to that in a minute). If the save succeeds, we empty out the cart ready for the next order and redisplay the catalog, using our redirect_to_index( ) method to display a cheerful message. If instead the save fails, we redisplay the checkout form.
One last thing before we call our customer over. Remember when we showed her the first product maintenance page? She asked us to add validation. We should probably do that for our checkout page too. For now we’ll just check that each of the fields in the order has been given a
Prepared exclusively for Rida Al Barazi
Report erratum

ITERATION D1: CAPTURING AN ORDER 102
Joe Asks. . .
Aren’t You Creating Duplicate Orders?
Joe’s concerned to see our controller creating Order model objects in two actions, checkout and save_order. He’s wondering why this doesn’t lead to duplicate orders in the database.
The answer is simple: the checkout action creates an Order object in memory simply to give the template code something to work with. Once the response is sent to the browser, that particular object gets abandoned, and it will eventually be reaped by Ruby’s garbage collector. It never gets close to the database.
The save_order action also creates an Order object, populating it from the form fields. This object does get saved in the database.
So, model objects perform two roles: they map data into and out of the database, but they are also just regular objects that hold business data. They affect the database only when you tell them to, typically by calling save( ).
|
value. We know how to do this—we add a validates_presence_of( ) call to the |
|
|
Order model. |
|
File 32 |
validates_presence_of :name, :email, :address, :pay_type |
|
|
So, as a first test of all of this, hit the Checkout button on the checkout |
|
|
page without filling in any of the form fields. We expect to see the checkout |
|
|
page redisplayed along with some error messages complaining about the |
|
|
empty fields. Instead, we simply see the checkout page—no error mes- |
|
|
sages. We forgot to tell Rails to write them out.3 |
|
|
Any errors associated with validating or saving a model are stored with that |
|
|
model. There’s another helper method, error_messages_for( ), that extracts |
|
|
and formats these in a view. We just need to add a single line to the start |
|
|
of our checkout.rhtml file. |
|
File 36 |
<%= error_messages_for("order") %> |
|
|
|
|
|
3If you’re following along at home and you get the message No action responded to |
|
|
save_order, it’s possible that you added the save_order( ) method after the private declaration |
|
|
in the controller. Private methods cannot be called as actions. |
Prepared exclusively for Rida Al Barazi
Report erratum

ITERATION D1: CAPTURING AN ORDER 103
Figure 9.2: Full House! Every Field Fails Validation
|
Just as with the administration validation, we need to add the scaffold.css |
|
stylesheet to our store layout file to get decent formatting for these errors. |
File 35 |
<%= stylesheet_link_tag "scaffold", "depot", :media => "all" %> |
|
Once we do that, submitting an empty checkout page shows us a lot of |
|
highlighted errors, as shown in Figure 9.2 . |
|
If we fill in some data as shown at the top of Figure 9.3, on page 105, and |
|
click Checkout , we should get taken back to the catalog, as shown at the |
|
bottom of the figure. But did it work? Let’s look in the database. |
Prepared exclusively for Rida Al Barazi
Report erratum