- •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
ITERATION C3: FINISHING THE CAR T |
90 |
David Says. . .
How Much Inline Error Handling Is Needed?
The add_to_cart( ) method shows the deluxe version of error handling in Rails where the particular error is given exclusive attention and code. Not every conceivable error is worth spending that much time catching. Lots of input errors that will cause the application to raise an exception occur so rarely that we’d rather just treat them to a uniform catchall error page.
Such an error page can be implemented in the ApplicationController where the rescue_action_in_public(exception) method will be called when an exception bubbles up without being caught at the lower levels. More on this technique in Chapter 22, Deployment and Scaling, on page 440.
File 23 |
def display_cart |
|
@cart = find_cart |
|
@items = @cart.items |
|
if @items.empty? |
|
flash[:notice] = "Your cart is currently empty" |
|
redirect_to(:action => 'index') |
|
end |
|
end |
|
Remember that on page 71 we set up the store layout to use the value in |
|
@page_title if it was defined. Let’s use that facility now. Edit the template |
|
display_cart.rhtml to override the page title whenever it’s used. This is a nice |
|
feature: instance variables set in the template are available in the layout. |
File 29 |
<% @page_title = "Your Pragmatic Cart" -%> |
|
Sensing the end of an iteration, we call our customer over and show her |
|
that the error is now properly handled. She’s delighted and continues to |
|
play with the application. She notices two things on our new cart display. |
|
First, the Empty cart button isn’t connected to anything (we knew that). |
|
Second, if she adds the same book twice to the cart, it shows the total price |
|
as 59.9 (two times $29.95), rather than $59.90. These two minor changes |
|
will be our next iteration. We should make it before heading home. |
8.5 Iteration C3: Finishing the Cart
Let’s start by implementing the Empty cart link on the cart display. We know by now that we have to implement an empty_cart( ) method in the store controller. Let’s have it delegate the responsibility to the Cart class.
Prepared exclusively for Rida Al Barazi
Report erratum
|
ITERATION C3: FINISHING THE CAR T |
91 |
|
def empty_cart |
|
|
find_cart.empty! |
|
|
flash[:notice] = 'Your cart is now empty' |
|
|
redirect_to(:action => 'index') |
|
|
end |
|
|
Over in the cart, we’ll implement the empty!( ) method. (Ruby method |
|
|
names can end with exlamation marks and question marks. We use a |
|
|
method name ending in an exclamation mark as a hint to future develop- |
|
|
ers that this method does something destructive.) |
|
File 28 |
def empty! |
|
@items = [] @total_price = 0.0
end
Now when we click the Empty cart link, we get taken back to the catalog page, and a nice little message says
|
However, before we break an arm trying to pat ourselves on the back, let’s |
|
look back at our code. We’ve just introduced two pieces of duplication. |
|
First, in the store controller, we now have three places that put a message |
|
into the flash and redirect to the index page. Sounds like we should extract |
|
that common code into a method, so let’s implement redirect_to_index( ) and |
|
change the add_to_cart( ), display_cart( ), and empty_cart( ) methods to use it. |
File 26 |
def add_to_cart |
|
product = Product.find(params[:id]) |
|
@cart = find_cart |
|
@cart.add_product(product) |
|
redirect_to(:action => 'display_cart') |
|
rescue |
|
logger.error("Attempt to access invalid product #{params[:id]}") |
|
redirect_to_index('Invalid product') |
|
end |
|
def display_cart |
|
@cart = find_cart |
|
@items = @cart.items |
|
if @items.empty? |
|
redirect_to_index("Your cart is currently empty") |
|
end |
|
end |
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION C3: FINISHING THE CAR T |
92 |
def empty_cart @cart = find_cart @cart.empty!
redirect_to_index('Your cart is now empty') end
private
def redirect_to_index(msg = nil) flash[:notice] = msg if msg redirect_to(:action => 'index')
end
empty!
→ page 479
private
→ page 473
|
Our second piece of duplication is in the cart model, where both the |
|
constructor and the empty! method do the same thing. That’s easily |
|
remedied—we’ll have the constructor call the empty!( ) method. |
File 28 |
def initialize |
|
empty! |
|
end |
|
def empty! |
|
@items = [] |
|
@total_price = 0.0 |
|
end |
Helpers
Our second task in this iteration is to tidy up the dollar amounts displayed in the cart. Rather than 59.9, we should be displaying $59.90.
Now we all know how to do this. We can just slap a call to the sprintf( ) method7 into the view.
<td align="right">
<%= sprintf("$%0.2f", item.unit_price) %>
</td>
But before we do that, let’s think for a second. We’re going to have to do this for every dollar amount we display. There’s some duplication there. What happens if our customer says later that we need to insert commas between sets of three digits, or represent negative numbers in parentheses? It would be better to extract monetary formatting out into a single method so we have a single point to change. And before we write that method, we have to decide where it goes.
Fortunately, Rails has an answer—it lets you define helpers. A helper is simply code in a module that is automatically included into your views. You define helper files in app/helpers. A helper named xyz_helper.rb defines methods that will be available to views invoked by the xyz controller. If you define helper methods in the file app/helpers/application_helper.rb, those
7Old C hippies will recognize sprintf( ) as the method that takes a string containing %x sequences. It substitutes its additional parameters for these sequences.
Prepared exclusively for Rida Al Barazi
Report erratum
|
ITERATION C3: FINISHING THE CAR T |
93 |
|
|
methods will be available in all views. As displaying dollar amounts seems |
|
|
|
to be a fairly universal thing, let’s add our method there. |
module |
|
File 27 |
# The methods added to this helper will be available |
→ page 473 |
|
|
|
||
|
# to all templates in the application. |
|
|
|
module ApplicationHelper |
|
|
|
def fmt_dollars(amt) |
|
|
|
sprintf("$%0.2f", amt) |
|
|
|
end |
|
|
|
end |
|
|
|
Then we’ll update our cart display view to use this new method. |
|
|
File 29 |
<% |
|
|
|
for item in @items |
|
|
|
product = item.product |
|
|
-%>
<tr>
<td><%= item.quantity %></td> <td><%= h(product.title) %></td>
<td align="right"><%= fmt_dollars(item.unit_price) %></td>
<td align="right"><%= fmt_dollars(item.unit_price * item.quantity) %></td>
</tr>
<% end %>
<tr>
<td colspan="3" align="right"><strong>Total:</strong></td> <td id="totalcell"><%= fmt_dollars(@cart.total_price) %></td>
</tr>
Now when we display the cart, the dollar amounts all look nicely formatted.
Now it’s time for a confession. At the time we wrote this sample application, Rails was a few releases away from version 1.0. Since then, a number of built-in helper methods have been added. One of these is the method number_to_currency( ), which would be a nice replacement for the fmt_dollars( ) method we just wrote. However, if we changed the book to use
Prepared exclusively for Rida Al Barazi
Report erratum
ITERATION C3: FINISHING THE CAR T |
94 |
the new method, we wouldn’t be able to show you how to write your own helpers, would we?
One last thing. On the catalog page (created by the index.rhtml template) we use sprintf( ) to format the product prices. Now that we have a handy-dandy currency formatter, we’ll use it there too. We won’t bother to show the template again here—the change is trivial.
What We Just Did
It’s been a busy day, but a productive one. We’ve added a shopping cart to our store, and along the way we’ve dipped our toes into some neat Rails features.
•Using sessions to store state
•Associating Rails models using belongs_to
•Creating and integrating nondatabase models
•Using the flash to pass errors between actions
•Using the logger to log events
•Removing duplication with helpers
So now the customer wants to see some checkout functionality. Time for a new chapter.
Prepared exclusively for Rida Al Barazi
Report erratum