标签: rubyonrails实例depot添加购物车 |
分类: rubyonrails学习 |
Rails使用基于cookie的做法来实现session的,所谓cookie是指web应用传递给浏览器的一组带命名的的数据。浏览器会将cookie保存在本地计算机上,当浏览器再向web应用发送请求时,会把cookie数据标签也一起带上,后者就可以根据cookie中的信息将这一请求与服务器保存的session信息匹配起来。Rails 为这些底层细节提供了一个简单的抽象接口,让开发者不必操心协议、cookie 之类的事情。在控制器中,Rails 维护了一个特殊的、类似于hash 的集合,名为session。在处理请求的过程中,如果你将一个名/值对保存在这个hash 中,那么在处理同一个浏览器发出的后续请求时都可以获取到该名/值对。
在Depot 应用程序中,我们希望在session 中保存“一个买主的购物车中有什么货品”的信息。Rails可以很容易地把session数据保存在数据库中。我们需要运行几个Rake任务来创建所需要的数据库表。首先,创建一个数据迁移任务来定义session数据表:rake db:sessions:create,然后实施这个迁移任务rake db:migrate,就把数据表创建出来了。我们需要告诉rails把session数据保存在数据库中,因为默认的是将所有东西都保存在cookie中,做法如下:首先将config目录下的environment.rb文件中的一行:#config.action_controller.session_store = :active_record_store前面的注释符号“#”去掉,激活基于数据库的session存储机制;然后打开app/controlle/application.rb,找到protect_from_forgery #:secret => 'aa8daee37f8fc7c6d44e9260bc6b8654'这一行,删掉中间的“#”。
当顾客执行“add to cart”动作时,我们需要做以下这些事情:如果顾客是第一次用到购物车,那么我们要为顾客建立一个购物车对象并将其放入session中,若顾客已经拥有购物车,则需要从session中取出这个对象。根据此功能,我们在store控制器中创建一个find_cart()方法,由于无需将这个方法作为这个控制器中的一个action了,所以声明为private,并要注意之后添加的方法若作为action的话一定要写在private之前。
private
def find_cart
session[:cart] ||= Cart.new
end
以上代码它使用了Ruby 的条件赋值操作符:||=。如果session 的hash 中已经有:cart这个键,上述语句会立即返回:cart 键对应的值;否则,它会首先新建一个cart 对象,将其放入session,并返回新建的对象。
还需要在store的控制器中加入action,名为add_to_cart的方法,这个方法的功能为:从当前的session中取出购物车对象(如果还没有购物车对象,就新建一个),将选中的货品放入购物车,并显示购物车的内容。
def add_to_cart
@cart = find_cart
product = Product.find(params[:id])
@cart.add_product(product)
end
修改views/store/目录下的index.html.erb文件,将id作为一个参数传递给butto_to方法,
<%= button_to "Add to Cart" , :action => 'add_to_cart', :id => product %>
params是Rails的一个重要对象,其中包含了浏览器请求传来的所有参数。按照惯例,params[:id]包含了将被action是偶那个的对象id(主键)。在视图中调用button_to时我们就已经用:id =>product把这个值设置好了。注意这个add_to_cart方法是一个action方法,所以不要再private之后。
再为这个方法添加一个视图文件:add_to_cart.html.erb:
<h2>Your Pragmatic Cart</h2>
<ul>
<% for item in @cart.items %>
<li><%= item.quantity %> × <%=h item.title %></li>
<% end %>
</ul>
其中quantity是商品的数量,我们在购物车中加入货品“数量”的信息,我们建立“购物车货品”模型cart_item,其中的一个对象不仅包括一个product,而且拥有数量的信息。cart_item.erb中的内容如下;
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.rb模型中的内容:
class Cart
attr_reader :items
def initialize
@items = []
end
def add_product(product)
current_item = @items.find{|item| item.product == product}
if current_item
current_item.increment_quantity
else
@items << CartItem.new(product)
end
end
end
这时,再次运行程序,打开http://127.0.0.1:3001/store,点击一下"Add to Cart"按键,页面跳转到如下图所示的页面:
退回后,再点击刚才的按键,则跳转到如下图所示的页面:
现在我们已经为买家提供了简单的购物车,但是,这个购物车令我们很担忧,我们可以看到,上图中将货品放入购物车的链接为:http://127.0.0.1:3001/store/add_to_cart/4,最后面的4就是product的id,如果我们手动将4改为其他不存在的id则页面会崩溃:
这个出错页面暴露了太多信息,大多数顾客是看不懂的,所以我们还应该进一步提升系统的安全性和可靠性。我们可以看到错误信息是因为product = Product.find(params[:id])这行代码,如果指定的货品找不到ActiveRecord 会抛出RecordNotFound 异常,我们处理此异常有三个部分:首先,利用Rails 的日志工具将这一事实记入内部日志文件;其次,向用户输出一条简短的信息(例如“非法货品”之类的);最后,重新显示分类列表页面,以便用户可以继续使用我们的站点。Rails中用flash来处理异常。
当add_to_cart() action 发现传入的货品id 不合法时,它就可以将错误信息保存在flash 中,并重定向到index() action,以便重新显示分类列表页面。index() action 的视图可以将flash 中的错误信息提取出来,在列表页面顶端显示。在视图中,使用flash 方法即可访问到flash 中的信息。flash 数据是保存在session 中的,这样才能够在多个请求之间传递。
修改store.rb中的add_to_cart方法:
def add_to_cart
product = Product.find(params[:id])
@cart = find_cart
@cart.add_product(product)
rescue ActiveRecord::RecordNotFound
logger.error("Attempt to access invalid product #{params[:id]}" )
flash[:notice] = "Invalid product"
redirect_to :action => 'index'
end
rescue 子句拦下了Product.find()抛出的异常,接着我们完成了上述的异常处理的三个部分。运行程序,进行同样的错误测试,打开log目录下的development.log,发现最后几行已经可以看到错误信息了:
Attempt to access invalid product hello
Redirected to actionindex
Completed in 16ms (DB: 16) | 302 Found [http://127.0.0.1/store/add_to_cart/hello]
但是此时的页面只是重定向到了store页面,没有显示出出错的原因,我们应该在视图文件中添加代码,使错误信息显示出来:向store.html.erb添加如下代码:
<% if flash[:notice] -%>
<div id="notice"><%= flash[:notice] %></div>
<% end -%>
将这三行代码放在<%= yield :layout %>这一行前面。这时再次输入错误的id,跳转结果如下所示:
看到了吧,我们处理异常的三个部分都完成了~很容易吧?
还有一个问题,也许你在测试程序的时候已经发现了,那就是我们没有办法在购物车页面删掉又不想购买的商品,这对于顾客是不公平的,下面我们来完成这个功能。我们需要在购物车中加上一个链接,同时在store 控制器中实现empty_cart()方法。
def empty_cart
session[:cart] = nil
flash[:notice] = "Your cart is currently empty"
redirect_to :action => 'index'
end
接着在add_to_cart页面中添加button按钮,在文件的最后添加:<%= button_to 'Empty cart', :action => 'empty_cart' %>