• No se han encontrado resultados

Acciones de mejora de cada uno de los puntos analizados.

6. PROPUESTA DE INTERVENCIÓN

6.1 Acciones de mejora de cada uno de los puntos analizados.

Observant readers (yes, that’s all of you) will have noticed that our catalog

listing view already includes anAdd to Cart link in each product listing.

File 79 <%= link_to 'Add to Cart',

{:action => 'add_to_cart', :id => product }, :class => 'addtocart' %><br/>

This link points back to anadd_to_cart( ) action in the store controller and

Report erratum

ITERATIONC1: CREATING ACAR T 79

will pass in the product id as a form parameter.3 Here’s where we start

to see how important theid field is in our models. Rails identifies model

objects (and the corresponding database rows) by theiridfields. If we pass

an id toadd_to_cart( ), we’re uniquely identifying the product to add.

Let’s implement the add_to_cart( ) method now. It needs to find the shop-

ping cart for the current session (creating one if there isn’t one there already), add the selected product to that cart, and display the cart con- tents. So, rather than worry too much about the details, let’s just write

the code at this level of abstraction. We’ll create anadd_to_cart( ) method

in app/controllers/store_controller.rb. It uses the params object to get theid parameter from the request and then finds the corresponding product, and

it uses thefind_cart( ) method we created earlier to find the cart in the ses-

sion and add the product to it. Be careful when you add theadd_to_cart( )

method to the controller. Because it is called as an action, it must be pub-

lic and so must be added above the private directive we put in to hide the

find_cart( ) method.

File 75 def add_to_cart

product = Product.find(params[:id]) @cart = find_cart

@cart.add_product(product)

redirect_to(:action => 'display_cart')

end

Clearly, this code isn’t going to run yet: we haven’t yet created aCartclass,

and we don’t have any implementation of thedisplay_cart( ) functionality.

Let’s start off with theCartclass and itsadd_product( ) method. As it stores

application data, it is logically part of our model, so we’ll create the file cart.rb in the directory app/models. However, it isn’t tied to a database

table, so it’s not a subclass ofActiveRecord::Base.

File 76 class Cart

attr_reader :items attr_reader :total_price def initialize @items = [] @total_price = 0.0 end def add_product(product) << page474 @items << LineItem.for_product(product) @total_price += product.price end end

3Saying:id => productis idiomatic shorthand for:id => product.id. Both pass the product’s id

back to the controller.

Report erratum

ITERATIONC1: CREATING ACAR T 80

That was pretty straightforward. We create a new line item based on the product and add it to the list. Of course, we haven’t yet got a method that creates a line item based on information in a product, so let’s rectify

that now. We’ll open up app/models/line_item.rb and add a class method

for_product( ). Creating these class-level helper methods is a neat trick for keeping your code readable.

File 77 class LineItem < ActiveRecord::Base

belongs_to :product

def self.for_product(product) self.new

page479 item = self.new

item.quantity = 1 item.product = product item.unit_price = product.price item end end

So, let’s see where we are. We created a Cartclass to hold our line items,

and we implemented the add_to_cart( ) method in the controller. That in

turn calls the newfind_cart( ) method, which makes sure that we keep the

cart object in the session.

We still need to implement thedisplay_cart( ) method and the corresponding

view. At the same time, we’ve written a whole lot of code without trying it out, so let’s just slap in a couple of stubs and see what happens. In the store controller, we’ll implement an action method to handle the incoming request.

File 75 def display_cart

@cart = find_cart @items = @cart.items

end

Over in theapp/views/storedirectory we’ll create a stub for the correspond-

ing view,display_cart.rhtml.

File 78 <h1>Display Cart</h1>

<p>

Your cart contains <%= @items.size %> items.

</p>

So, with everything plumbed together, let’s have a look at our store in a

browser. Navigating tohttp://localhost:3000/storebrings up our catalog page.

Click on theAdd to Cart links for one of the products.4 We expect to see

the stub cart display, but instead we are faced with a somewhat brutal

4If you don’t see a list of products, you’ll need to go back to the administration section of

the application and add some.

Report erratum

ITERATIONC1: CREATING ACAR T 81

page. (Although not in Rails 0.13.1 or later, where the steps on this page are no longer necessary.)

At first, we might be tempted to think that we’d mispelled the name of the action method or the name of the view, but that’s not the case. This isn’t a Rails error message—it comes straight from WEBrick. To find out what’s going on, we need to look at the WEBrick console output. Find the window where WEBrick is running, and you’ll see it is full of logging and trace messages. The trace indicates that something has gone wrong in the application. (Technically, this is a stack backtrace, which shows the chain of method calls that got us to the point where the application choked.) The easy way to find out what went wrong is to scroll back through the trace. Just before it starts, you’ll see an error message.

#<ActionController::SessionRestoreError: Session contained

objects where the class definition wasn't available. Remember to require classes for all objects kept in the session. The session has been deleted.>

When Rails tried to load the session information from the cookie that came from the browser, it came across some classes that it didn’t know about.

We’ll have to tell Rails about ourCartandLineItemclasses. (The sidebar on

page83explains why.) Over inapp/controllersyou’ll find a file calledappli-

cation.rb. This file is used to establish a context for the entire application.

By default, it contains an empty definition of class ApplicationController.

We’ll need to add two lines to this class to declare our new model files.

File 74 class ApplicationController < ActionController::Base

model :cart model :line_item

end

Now if we hit Refresh in our browser, we should see our stub view displayed

(see Figure8.2, on the next page). If we use the Back button to return to

the catalog display and add another product to the cart, you’ll see the

Report erratum

ITERATIONC1: CREATING ACAR T 82

Figure 8.2: Our Cart Now Has an Item in It

count update when the display cart page is displayed. It looks like we have sessions working.

Phew! That was probably the hardest thing we’ve done so far. It was definitely the longest time we’ve spent without having anything to show our customer (or ourselves). But now that we have everything linked together correctly, let’s quickly implement a simple cart display so we can get some

customer feedback. We’ll replace the stub code in display_cart.rhtml with

code that displays the items in the cart.

File 82 <h1>Display Cart</h1>

<table> <%

for item in @items product = item.product -%>

<tr>

<td><%= item.quantity %></td>

<td><%= h(product.title) %></td>

<td align="right"><%= item.unit_price %></td>

<td align="right"><%= item.unit_price * item.quantity %></td>

</tr> <% end -%>

</table>

This template illustrates one more feature of ERb. If we end a piece of

embedded Ruby with-%>(note the extra minus sign), ERb will suppress the

newline that follows. That means embedded Ruby that doesn’t generate any output won’t end up leaving extraneous blank lines in the output.

Report erratum

ITERATIONC1: CREATING ACAR T 83

Session Information, Serialization, and Classes

The session construct stores the objects that you want to keep around between browser requests. To make this work, Rails has to take these objects and store them at the end of one request, loading them back in when a subsequent request comes in from the same browser. To store objects outside the running application, Rails uses Ruby’s serialization mechanism, which converts objects to data that can be loaded back in. However, that loading works only if Ruby knows about the classes of the objects in the serialized file. When we store a cart in a session, we’re storing an object of classCart. But when it comes time to load that data back in, there’s no guarantee that Rails will have loaded up theCartmodel at that point (because Rails loads things only when it thinks it needs them). Using themodeldeclaration forces Rails to load the user model class early, so Ruby knows what to do with it when it loads the serialized session.

Hit Refresh in our browser, and (assuming we picked a product from the catalog) we’ll see it displayed.

1 Pragmatic Project Automation 29.95 29.95

Hit the Back button (we’ll work on navigation shortly), and add another.

1 Pragmatic Project Automation 29.95 29.95 1 Pragmatic Version Control 29.95 29.95

Looking good. Go back and add a second copy of the original product.

1 Pragmatic Project Automation 29.95 29.95 1 Pragmatic Version Control 29.95 29.95 1 Pragmatic Project Automation 29.95 29.95

That’s not so good. Although the cart is logically correct, it’s not what our users would expect. Instead, we should probably have merged both of those automation books into a single line item with a quantity of 2.

Fortunately, this is a fairly straightforward change to the add_product( )

method in theCartmodel. When adding a new product, we’ll look to see if

that product is already in the cart. If so, we’ll just increment its quantity rather than adding a new line item. Remember that the cart is not a database object—this is just straight Ruby code.

File 81 def add_product(product)

item = @items.find {|i| i.product_id == product.id}

if item

item.quantity += 1

Report erratum

ITERATIONC1: CREATING ACAR T 84 else item = LineItem.for_product(product) @items << item end @total_price += product.price end

The problem we now face is that we already have a session with duplicate products in the cart, and this session is associated with a cookie stored in

our browser. It won’t go away unless we delete that cookie.5 Fortunately,

there’s an alternative to punching buttons in a browser window when we want to try out our code. The Rails way is to write tests. But that’s such a

big topic, we’re putting it in its own chapter, Chapter12,Task T: Testing,

on page132.

Tidying Up the Cart

Before we declare this iteration complete and we show our customer our application so far, let’s tidy up the cart display page. Rather than simply dumping out the products, let’s add some formatting. At the same time,

we can add the much-neededContinue shopping link so we don’t have to

keep hitting the Back button. While we’re adding links, let’s anticipate a little and add links to empty the cart and to check out.

Our newdisplay_cart.rhtml file looks like this.

File 25 <div id="cartmenu">

<ul>

<li><%= link_to 'Continue shopping', :action => "index" %></li>

<li><%= link_to 'Empty cart', :action => "empty_cart" %></li>

<li><%= link_to 'Checkout', :action => "checkout" %></li>

</ul>

</div>

<table cellpadding="10" cellspacing="0">

<tr class="carttitle"> <td rowspan="2">Qty</td> <td rowspan="2">Description</td> <td colspan="2">Price</td> </tr> <tr class="carttitle"> <td>Each</td> <td>Total</td> </tr> <%

for item in @items product = item.product -%>

<tr>

<td><%= item.quantity %></td>

5Which you can do if you want. You can delete the cookie file (on a Unix box it will be in

a file whose name startsruby_sessin your/tmpdirectory. Alternatively, look in your browser for a cookie fromlocalhostcalled_session_id, and delete it.

Report erratum

ITERATIONC1: CREATING ACAR T 85

Figure 8.3: On Our Way to a Presentable Cart

<td><%= h(product.title) %></td>

<td align="right"><%= item.unit_price %></td>

<td align="right"><%= item.unit_price * item.quantity %></td>

</tr> <% end %>

<tr>

<td colspan="3" align="right"><strong>Total:</strong></td>

<td id="totalcell"><%= @cart.total_price %></td>

</tr>

</table>

After a bit of CSS magic, our cart looks like Figure8.3.

Happy that we have something presentable, we call our customer over and show her the result of our morning’s work. She’s pleased—she can see the site starting to come together. However, she’s also troubled, having just read an article in the trade press on the way e-commerce sites are being attacked and compromised daily. She read that one kind of attack involves feeding requests with bad parameters into web applications, hoping to expose bugs and security flaws. She noticed that the link to add an item to

our cart looks likestore/add_to_cart/nnn, wherennnis our internal product

id. Feeling malicious, she manually types this request into a browser, giving it a product id of "wibble." She’s not impressed when our application

displays the page in Figure8.4, on the following page. So it looks as if our

next iteration will be spent making the application more resilient.

Report erratum

ITERATIONC2: HANDLINGERRORS 86

Figure 8.4: Our Application Spills Its Guts

Documento similar