Rails使用基于cookie的做法来实现session的,所谓cookie是指web应用传递给浏览器的一组带命名的的数据。浏览器会将cookie保存在本地计算机上,当浏览器再向web应用发送请求时,会把cookie数据标签也一起带上,后者就可以根据cookie中的信息将这一请求与服务器保存的session信息匹配起来。Rails 为这些底层细节提供了一个简单的抽象接口,让开发者不必操心协议、cookie 之类的事情。在控制器中,Rails 维护了一个特殊的、类似于hash 的集合,名为session。在处理请求的过程中,如果你将一个名/值对保存在这个hash 中,那么在处理同一个浏览器发出的后续请求时都可以获取到该名/值对。
在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
分类显示
ruby script/generate controller store index
class StoreController < ApplicationController
def index
@products = Product.find_products_for_sale
end
end
class Product < ActiveRecord::Base
def self.find_products_for_sale
find(:all, :order => "title" )
end
depot_d/app/views/store/index.html.erb
<h1>Your Pragmatic Catalog</h1>
<% for product in @products -%>
<div class="entry">
<%= image_tag(product.image_url) %>
<h3><%=h product.title %></h3>
<%= product.description %>
<div class="price-line">
<span class="price"><%= product.price %></span>
</div>
</div>
<% end %>
h(string)方法对货品名称中出现的HTML 元素进行转义处理,但并没有对货品
描述做同样的处理。这样一来,我们就可以在其中加入HTML 样式
添加页面布局
在Rails 中,定义和使用布局的方式有好多种,我们现在就使用最简单的一种。如果我们在app/views/layouts目录中创建了一个与某个控制器同名的模板文件,那么该控制器所渲染的视图在默认状态下会使用此布局模板。所以,我们现在就来创建这样一个模板。我们的控制器名叫store ,所以这个布局模板的名字就应该是store.html.erb 。
<!DOCTYPE html PUBLIC "//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd" >
<html>
<head>
<title>Pragprog Books Online Store</title>
<%= stylesheet_link_tag "depot" , :media => "all" %>
</head>
<body id="store">
<div id="banner">
<%= image_tag("logo.png" ) %>
<%= @page_title || "Pragmatic Bookshelf" %>
</div>
<div id="columns">
<div id="side">
<a href="http://www....">Home</a><br />
<a href="http://www..../faq">Questions</a><br />
<a href="http://www..../news">News</a><br />
<a href="http://www..../contact">Contact</a><br />
</div>
<div id="main">
<%= yield :layout %>
</div>
</div>
</body>
</html>
当我们调用yield 方法并传入:layout时, Rails 会自动在这里插入页面的内容
用辅助方法格式化价格
<span class="price" ><%= product.price %></span>
修改为
<span class="price"><%= number_to_currency(product.price) %></span>
链接到购物车
link_to()辅助方法生成的是<a href=...> 这样一个HTML 标签;当你点击链接时,浏览器会向服务器发起一个HTTP GET 请求。而HTTP GET 请求不应该用来改变服务器上任何东西的状态——只应该用于获取信息。
:method=>:post 可以解决这个问题。此外button_to()方法也可以从视图链回应用程序,但它生成的是一个HTML 表单,其中只包含一个按钮。当用户点击这个按钮时,浏览器会向服务器发起一个HTTP POST 请求。
<div class="price-line">
<span class="price"><%= number_to_currency(product.price) %></span>
<%= button_to "Add to Cart" %>
</div>
或者
private
def find_cart
unless session[:cart] # i除非存在了,否则新建一个
session[:cart] = Cart.new # add a new one
end
session[:cart] # return existing or new cart
end
修改views/store/目录下的index.html.erb文件,将id作为一个参数传递给butto_to方法,
创建购物车
创建cart.rb文件,把它放在app/models 目录
class Cart
attr_reader :items
def initialize
@items = []
end
def add_product(product)
@items << product
end
end
另一个聪明一点的购物车
给购物车中的货品加上“数量”信息。于是我们决定新建一个模型类CartItem ,其中引用一种货品,并包含数量信息。 每次初始化一个CartItem,数量为一。
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()方法中使用这个新建的模型类了
def add_product(product)
current_item = @items.find {|item| item.product == product}
#判断是否存在,存在当前的item,则购物车数量增加1,否则把CartItem放进items里头
if current_item
current_item.increment_quantity
else
@items << CartItem.new(product)
end
end
对add_to_cart视图做了简单的修改,将“数量”这项新的信息显示出来。
<li><%= item.quantity %> × <%=h item.title %></li>
add_to_cart这个方法添加一个视图文件:add_to_cart.html.erb:
点击Add to Cart按钮
NoMethodError in StoreController#add_to_cart
undefined method `product' for #<Product:0x3880b00>
session 中的购物车对象还是旧版本的:它直接把货品放进@items 数组。所以,当Rails 从session 中取出这个购物车之后,里面装的都是Product 对象,而不是CartItem 对象。
最简单的办法就是删除旧的session,把原来那个购物车留下的所有印记都抹掉。由于我们使用数据库来保存session 数据,只要一个Rake 命令就可以轻松地清空sessions 表。
rake db:sessions:clear
假设Depot 应用已经发布了一个版本,其中使用了旧的Cart 对象。现在有成千上万的顾客正在忙碌地购物,而我们又决定要发布新的、更好用的购物车。我们把代码投入正式运行,突然间所有正在享受购物乐趣的顾客们在往购物车中添加货品时就会遇到错误。而我们唯一的解决办法是删除session数据,也就是说所有顾客的购物车都会被清空。
这个例子告诉我们,“把应用层面的对象放在session中”通常不是个好主意:如果这样做的话,一旦我们把修改之后的应用程序投入实际运行,就必须清空所有现存的session数据。
所以,推荐的做法是只在session 中保存尽可能简单的东西:字符串、数字,等等。应用层面的对象应该放在数据库里,然后将它们的主键放入session,需要时根据session中的主键来查找对象。