Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Agile Web Development With Rails, 1st Edition (2005).pdf
Скачиваний:
28
Добавлен:
17.08.2013
Размер:
7.99 Mб
Скачать

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