Agile Web Development with Rails 翻譯(十四)
2006年4月17日更新
8.3 循環(huán) C1: 創(chuàng)建個(gè)購(gòu)物車(chē)
讀者可能注意到了我們的分類(lèi)目錄列表“視圖”已經(jīng)包含了一個(gè)Add to Cart連接給每個(gè)產(chǎn)品列表。
<%= link_to ‘Add to Cart‘,
{:action => ‘a(chǎn)dd_to_cart‘, :id => product },
:class => ‘a(chǎn)ddtocart‘ %><br/>
這個(gè)連接點(diǎn)由store內(nèi)“控制器”的add_to_cart()“動(dòng)作”支持,并會(huì)傳遞產(chǎn)品的id做為表單的參數(shù)。[說(shuō):id=>product,是:id=>product.id的習(xí)慣縮寫(xiě)。兩者將產(chǎn)品的id傳回給“控制器”。]從這里我們看到了我們“模型”內(nèi)的id字段是如何的重要了。Rails通過(guò)它們的id字段標(biāo)識(shí)“模型”對(duì)象(及相應(yīng)的數(shù)據(jù)庫(kù)的行)。如果我們傳遞一個(gè)id給add_to_cart(),我們會(huì)添加具有唯一標(biāo)識(shí)的產(chǎn)品。
現(xiàn)在,讓我們實(shí)現(xiàn)add_to_cart()方法。它需要為當(dāng)前“會(huì)話(huà)”(如果沒(méi)有則創(chuàng)建一個(gè))找到購(gòu)物車(chē),并添加選擇的產(chǎn)品到這個(gè)購(gòu)物車(chē)中,然后顯示此購(gòu)物車(chē)中的內(nèi)容。由于細(xì)節(jié)并不很麻煩,讓我們只寫(xiě)出抽象級(jí)代碼。我們將在app/controllers/store_controller.rb文件內(nèi)創(chuàng)建一個(gè)add_to_cart()方法。它使用參數(shù)對(duì)象來(lái)從請(qǐng)求中獲得id參數(shù),然后找出相應(yīng)產(chǎn)品,并使用我們先前創(chuàng)建的find_cart()方法來(lái)找到此“會(huì)話(huà)”內(nèi)的購(gòu)物車(chē),并添加產(chǎn)品給此購(gòu)物車(chē)。當(dāng)給“控制器”添加add_to_cart()方法時(shí)要小心。因?yàn)樗蛔鳛橐粋€(gè)“動(dòng)作”來(lái)調(diào)用,它必須是public的,所以必須被添加到private的find_cart()方法的上面。
def add_to_cart
product = Product.find(params[:id])
@cart = find_cart
@cart.add_product(product)
redirect_to(:action => ‘display_cart‘)
end
很明顯,這個(gè)代碼也不會(huì)運(yùn)行:我們沒(méi)有創(chuàng)建Cart類(lèi),并且我們也沒(méi)對(duì)display_cart()功能的任何實(shí)現(xiàn)。
讓我們開(kāi)始創(chuàng)建Cart類(lèi)和它的add_product()方法。因?yàn)樗鎯?chǔ)應(yīng)用程序數(shù)據(jù),它是我們“模型”的邏輯部分,所以我們將創(chuàng)建文件cart.rb在目錄app/models內(nèi)。雖然,它與數(shù)據(jù)庫(kù)表沒(méi)有聯(lián)系,因此它不是ActiveRecord::Base的子類(lèi)。
class Cart
attr_reader :items
attr_reader :total_price
def initialize
@items = []
@total_price = 0.0
end
def add_product(product)
@items << LineItem.for_product(product)
@total_price += product.price
end
end
這很直截了當(dāng)。我們基于產(chǎn)品創(chuàng)建了一個(gè)新的商品項(xiàng)并添加它到列表中。當(dāng)然,我們也沒(méi)有一個(gè)方法來(lái)創(chuàng)建一個(gè)基于某個(gè)產(chǎn)品信息的商品項(xiàng),所以讓我們現(xiàn)在調(diào)整一下。我們會(huì)打開(kāi)app/models/line_item.rb文件,并添加一個(gè)類(lèi)方法for_product()給它。創(chuàng)建這類(lèi)級(jí)別的方法是為了讓你的代碼整潔易于閱讀。
class LineItem < ActiveRecord::Base
belongs_to :product
def self.for_product(product)
item = self.new
item.quantity = 1
item.product = product
item.unit_price = product.price
item
end
end
現(xiàn)在我們創(chuàng)建了一個(gè)Cart類(lèi)來(lái)保持我們的商品項(xiàng),并且我們?cè)?#8220;控制器”內(nèi)實(shí)現(xiàn)了add_to_cart()方法。并依次調(diào)用新的find_cart()方法,這個(gè)方法確保我們保持“會(huì)話(huà)”內(nèi)的購(gòu)物車(chē)對(duì)象。
我們還需要實(shí)現(xiàn)display_cart()方法和相應(yīng)的“視圖”。同時(shí),我們已經(jīng)寫(xiě)了這么多代碼卻沒(méi)有嘗試,讓我們加入一些模擬數(shù)據(jù)(stub)來(lái)看看怎么樣。在store “控制器”中,我們將實(shí)現(xiàn)一個(gè)“動(dòng)作”方法來(lái)處理引入的請(qǐng)求。
def display_cart
@cart = find_cart
@items = @cart.items
end
在app/views/store目錄內(nèi),我們會(huì)為相應(yīng)的“視圖”創(chuàng)建個(gè)display_cart.rhtml文件。
<h1>Display Cart</h1>
<p>
Your cart contains <%= @items.size %> items.
</p>
我們已準(zhǔn)備好了所有東西,現(xiàn)在讓我們?cè)跒g覽器內(nèi)看看我們的商店。導(dǎo)航到http://localhost:300/stroe調(diào)用我們的分類(lèi)目錄頁(yè)。單擊每個(gè)產(chǎn)品的Add to Cart連接。[如果你沒(méi)有看到產(chǎn)品列表,你將需要退回到應(yīng)用程序的管理一節(jié)。]我們期望看到購(gòu)物車(chē)顯示頁(yè)面,但我們看到的卻是慘不忍睹的頁(yè)面。
首先,我們可能會(huì)想到我們拼錯(cuò)了“動(dòng)作”方法的名字或者是“視圖”的名字,但事實(shí)不是這樣。這不是Rails的錯(cuò)誤消息—它來(lái)自于WEBrick。想找出原因,我們需要看看WEBrick的控制臺(tái)輸出。進(jìn)入WEBrick的運(yùn)行窗口,你會(huì)看到登錄和跟蹤消息。跟蹤指出了應(yīng)用程序內(nèi)錯(cuò)誤的原因。(技術(shù)上說(shuō),這是個(gè)stack backtrace,它顯示了應(yīng)用程序阻塞點(diǎn)調(diào)用的方法鏈。)可以很容易地通過(guò)回卷來(lái)找出錯(cuò)誤。在開(kāi)始前,你將看到一個(gè)錯(cuò)誤信息。
#<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.>
當(dāng)Rails試圖加載來(lái)自于瀏覽器cookie的“會(huì)話(huà)”信息時(shí),它會(huì)遍歷一些它還不知道的類(lèi)。我們必須告訴Rails有關(guān)我們Cart和LineItem類(lèi)。(83頁(yè)的注釋解釋了為什么。)在app/controllers目錄內(nèi)你會(huì)找到個(gè)名為application.rb的文件。這個(gè)文件用于構(gòu)建應(yīng)用程序入口的上下文環(huán)境。缺省情況下,它包含一個(gè)空類(lèi)ApplicationController的定義。我們需要在其中添加兩行來(lái)聲明我們的新“模型”文件。
class ApplicationController < ActionController::Base
model :cart
model :line_item
end
現(xiàn)在,如果我們刷新我們的瀏覽器,我們應(yīng)該看到“視圖”顯示了。(如圖8.2)如果我們使用Back按鈕返回分類(lèi)目錄顯示,并添加另一個(gè)產(chǎn)品給購(gòu)物車(chē),你將會(huì)看到當(dāng)購(gòu)物車(chē)頁(yè)被顯示時(shí),計(jì)數(shù)被更新了。看起來(lái)們的“會(huì)話(huà)”工作了。
現(xiàn)在最困難的事情我們已做完了。這確實(shí)是我們能夠?yàn)槲覀兊目蛻?hù)展示什么之前所花費(fèi)的最久時(shí)間。 但是現(xiàn)在我們應(yīng)該將每個(gè)東西正確地連在一起,讓我們快速地實(shí)現(xiàn)一個(gè)簡(jiǎn)單的購(gòu)物車(chē)顯示,以便我們盡快得到客戶(hù)的反饋。我們用下面的代碼來(lái)替換原有購(gòu)物車(chē)內(nèi)的display_cart.rhtml文件內(nèi)代碼。
<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>
這個(gè)“模板”顯示了很多ERb特性。如果我們用-%>(注意有個(gè)減號(hào))來(lái)結(jié)束植入的Ruby語(yǔ)句,ERb將抑止隨后的新行。這意味著被植入的Ruby不能產(chǎn)生任何輸出。
-------------------------------------------------------------------------------
“會(huì)話(huà)”信息,序列化,和類(lèi)
Session結(jié)構(gòu)存儲(chǔ)瀏覽器請(qǐng)求間你想保持的對(duì)象。要想工作,Rails必須能接受這些對(duì)象,并存儲(chǔ)它們?cè)谝粋€(gè)請(qǐng)求的尾部,當(dāng)同一瀏覽器有后續(xù)請(qǐng)求時(shí)將它們加載回來(lái)。要在運(yùn)行的應(yīng)用程序外部存儲(chǔ)對(duì)象,Rails使用了Ruby的序列化機(jī)制,它轉(zhuǎn)換對(duì)象為可被稍后能重新取回的數(shù)據(jù)。當(dāng)我們?cè)谝粋€(gè)“會(huì)話(huà)”內(nèi)存儲(chǔ)一個(gè)購(gòu)物車(chē)時(shí),我們存儲(chǔ)了類(lèi)Cart的一個(gè)對(duì)象。但是當(dāng)加載回?cái)?shù)據(jù)時(shí),Rails并不保證加載此點(diǎn)上的原有Cart “模型”(因?yàn)?/span>Rails只加載它認(rèn)為它需要東西)??梢允褂?#8220;模型”聲明來(lái)強(qiáng)制Rails加載先前用戶(hù)“模型”類(lèi),所以當(dāng)Ruby加載序列化的“會(huì)話(huà)”時(shí),它知道它在做什么。
-------------------------------------------------------------------------------
刷新瀏覽器,(假設(shè)我們從分類(lèi)目錄中選擇了一個(gè)產(chǎn)品)我們會(huì)看到它顯示。
1 Pragmatic Project Automation 29.95 29.95
單擊Back按鈕,添加另一個(gè)產(chǎn)品。
1 Pragmatic Project Automation 29.95 29.95
1 Pragmatic Version Control 29.95 29.95
看起來(lái)不錯(cuò),返回并再選擇原有產(chǎn)品一次。
1 Pragmatic Project Automation 29.95 29.95
1 Pragmatic Version Control 29.95 29.95
1 Pragmatic Project Automation 29.95 29.95
這看起來(lái)可不好,盡管購(gòu)物車(chē)邏輯上是正確的,但它與我們想的不一樣。相反,我們或許應(yīng)該將兩者自動(dòng)地合并成一個(gè)數(shù)量為2的單獨(dú)的行條目。
幸運(yùn)地,這改起來(lái)很容易,通過(guò)給Cart “模型”添加add_product()方法。當(dāng)添加一個(gè)新產(chǎn)品時(shí),我們會(huì)看到產(chǎn)品已在那個(gè)購(gòu)物車(chē)內(nèi)了。如果是這樣話(huà),我們將只增加它的數(shù)量,而不添加個(gè)新的商品項(xiàng)。記住購(gòu)物車(chē)不是個(gè)數(shù)據(jù)庫(kù)對(duì)象—它只是Ruby代碼。
def add_product(product)
item = @items.find {|i| i.product_id == product.id}
if item
item.quantity += 1
else
item = LineItem.for_product(product)
@items << item
end
@total_price += product.price
end
我們現(xiàn)在面對(duì)的問(wèn)題是,我們?cè)谫?gòu)物車(chē)內(nèi)已經(jīng)有個(gè)一個(gè)帶有重復(fù)產(chǎn)品的“會(huì)話(huà)”,這個(gè)“會(huì)話(huà)”與我們?yōu)g覽器內(nèi)存儲(chǔ)的一個(gè)cookie相關(guān)聯(lián)。它不會(huì)自動(dòng)離去,除非我們刪除那個(gè)cookie。[如果你想的話(huà),你可這樣做。你可以刪除cookie文件。] 幸運(yùn)的是,當(dāng)我們想測(cè)試我們的代碼時(shí)有除了點(diǎn)擊瀏覽器按鈕以外的方法。Rails的方法是寫(xiě)測(cè)試。 但是這是個(gè)很大的題目,我們把它單獨(dú)地放在了一章,132頁(yè)的第十二章。
聯(lián)系客服