发表于:2006.08.13 12:27
分类: prototype
出处:http://my4java.itpub.net/post/9983/191010
---------------------------------------------------------------
第二版 Task C: Cart Creation (二)
8.3 迭代 C2: A Smarter Cart
对于真正的购物车,相同商品不会分别以两行来显示。所以还必须找出一种关联购物车内每个商品数量的办法。我们创建个新模型类,CartItem,它包含了对商品及其数量的引用。(my4java:也就是要在Cart与Product之间加上一个中间层CartItem。该中间层是对Product的再次包装,其具有quantity属性,而达到Cart对同一商品数量显示的要求。即Cart -----> CartItem ------> Product。)
class CartItem
attr_reader :product, :quantity
def initialize(product)
@product = product
@quantity = 1
end
def increment_quantity
@quantity += 1
end
def title
@product.title
end
def price
@product.price * @quantity
end
end
现在,在Cart的add_product()方法内使用它。我们看看项目列表是否已经包含了要添加的商品,如果包含了就修改数量,否则就添加个新的CartItem。
def add_product(product)
# 检查该商品是否出现过。
existing_product = @items.find {|item| item.product == product}
if existing_product
existing_product.increment_quantity #修改相应商品的数量
else
@items << CartItem.new(product) #存入到Cart对象的据组内
end
end
对add_to_cart模板也做些修改以使用这个新信息。
<h1>Your Pragmatic Cart</h1>
<ul>
<% for cart_item in @cart.items %>
<li><%= cart_item.quantity %> × <%= h(cart_item.title) %></li>
<% end %>
</ul>
刷新浏览器会看到错误。我们首先想到的是拼错了cart_item.rb内的quantity()属性,但事实上并没有写错,仔细观察错误信息。它说 “undefined method ‘quantity’ for #<Product:...>.” 。这意味着它认为购物车内的项目是products对象而不是CartItem对象。
我们知道Rails在development模式下会对新请求自动重新加载程序文件。但事实上,Rails只重新加载那些它知道的文件。Rails只知道标准Rails类的子类,并适当地管理它。但我们的Cart类是个自由的Ruby类,Rails并不知道要重新加载它。因此我们通过包含单个include指令来告诉Rails重新加载我们的模型。
class Cart
include Reloadable
# .. rest of model
class CartItem
include Reloadable
# .. rest of model
Including Reloadable 是说“把Cart与CartItem类看做与其它Rails模型一样,并在每个请求时重新加载它们”但是,因为当前运行的源代码并没有此指令,所以必须停止并重启应用程序来加载Cart和CartItem的新版本。以后要记住为每个非数据库模型添加include Reloadable 行,就不必重新启动应用程序了。
重启应用程序后,刷新浏览器,出现了与前面不同的异常。
它抱怨说我们试图在add_to_cart方法内的类Product对象上调用product()方法。但查看源代码,我们只引用了product()方法一次,即在CartItem对象上调用了它。为什么在代码明确地用CartItem组装它时,它认为@items数组包含了products。
要回答这个问题,我们必须回答我们添加产品的购物车来自于哪儿。不错它在会话内。会话内的购物车还是旧版本,在该版本中我们只是简单地附加产品给@items数组。因此在Rails从会话中得到购物车时,它得到了装有Product对象而不是CartItem对象的购物车,所以就出问题了。
修正此问题最容易的办法是删除旧的会话,移除所有原有的会话实现。我们使用了基于数据库的会话,我们可以rake来清除session表。
depot> rake db:sessions:clear
现在刷新浏览器,应用程序运行了新的购物车及add_to_view视图。
一、The Moral of the Tale
前面对Cart类的简单修改实际上引起两个不同的问题。
首先是Cart与CartItem类没有被重新加载:因为不是Rails类的子类,Rails不能自动地重新加载它们。我们通过给每个模型添加include Reloadable来修改此问题。
其次,会话数据包含了旧版本的Cart对象,它没有与新的源文件一致。我们通过清除旧的会话数据改正了它。
第一问题只是个简单的熟练问题。无论何时创建一个非数据库模型时,都需要在源代码中包含一个include Reloadable指令。但第二个问题就要有技巧了。这是由于在会话数据内存储完整对象引起的,当我们潜在地与这个数据不一致时,就会在运行时导致错误。这只是个developemt期间的错误。
如果说我们回滚Depot应用程序的一个版本,但使用旧版本的Cart。而正有着上千个消费者在忙着购物。然后我们决定回滚到新的,改良的Cart模型。代码执行,突然间所有在中途购物的消费者会发现添加商品到购物车时,它们会得到错误。我们只有通过删除会话数据来修正问题,但却丢失了消费者的购物车。
这告诉我们在会话数据内存储应用程序级别的对象其实不是个好主意。对应用程序的任何修改都可能潜在地要求我们丢失现有会话。
相反,我们推荐在会话内只存储简单的东西:字符串,数字等等。而在数据库内保存应用程序对象,然后使用它们的主键从会话数据中引用它们。对于Depot应用程序,我们明智地让Cart类成为一个活动记录对象,并存储cart数据在数据库内。然后会话存储cart对象的id。当一个请求到达时,我们从会话内抽取id,然后从数据库内加载cart。(事实上,我们可以抽象此功能在一个过滤器内,然后它就会自动发生。)虽然在你更新应用程序时,这并不能自动捕获所有问题,但它给你一个处理问题的机会。
总之,我们现在有了一个能管理产品数量的购物车,并可在视图内显示数量。
8.4 迭代 C3: 处理错误
注意store控制器内的这行代码会在输入无效id时出现错误。
product = Product.find(params[:id])
如果产品没有找到,活动记录抛出一个RecordNotFound异常,很明显它需要被处理。但此异常是如何引起的呢?
在异常抛出时,我们采取三个动作。首先,使用Rails的logger功能日志它。其次,给用户显示了简短消息。第三,重新显示分类清单页面,以便消费者能继续购物。
一、Flash 结构
Rails有个方便的方式来处理错误及报告错误。它定义了一个称为flash的结构。Flash是个桶(实际上更像一个哈希表),你可在其中存储你处理一个请求的东西。会话内的Flash内容在被自动删除之前,在下个请求内是有效的。典型地flash用于收集错误消息。例如,当add_to_cart()动作察觉到传递的参数是无效的产品id时,它可以存储那个错误消息在flash内并重定向到index()动作来重新显示分类清单。用于index动作的视图可以抽取这个错误并在分类页面的顶部显示它。Flash信息可通过使用flash存取器方法在视图中访问。
为什么不能存储错误在任何现有实例变量内呢?记住,一个重定向是由应用程序发送给浏览器的,然后它发送回应用程序一个新的请求。这时我们收到那个新请求,应用程序向前运行---所有来自先前请求的实例变量都不再存在了。存储在会话内的flash数据就是为了让它在两个请求之间有效。
使用后台的flash数据防止错误,我们现在修改add_to_cart()方法来中途截取错误的产品id,并报告此问题。
def add_to_cart
begin
@product = Product.find(params[:id])
rescue
logger.error("Attempt to access invalid product #{params[:id]}" )
flash[:notice] = "Invalid product"
redirect_to :action => :index
else
@cart = find_cart
@cart.add_product(@product)
end
end
rescue子句截获由Prouct.find()抛出的异常。在处理器内我们:
1、使用Rails的logger来记录该错误。每个控制器都有一个loger属性。这儿使用它来记录error级别消息。
2、创建带有错误说明的notice键flash。就像使用会话,你把flash当做一个哈希表来访问。这儿使用键 :notice 来存储消息。






