V 任务E:更智能的购物车
·个性数据库模式(schema)与现有数据
·诊断和处理错误
·闪存
·日志
一、迭代E1:创建更智能的购物车
1.由于购物车中每个产品都有一个关联的计数器,这就要求个性line_items表。
Administrator@JARRY /e/works/ruby/depot (master) $ rails generate migration add_quantity_to_line_items quantity:integer invoke active_record create db/migrate/20130326064705_add_quantity_to_line_items.rb
rails有两种匹配模式:add_XXX_to_TABLE和remove_XXX_to_TABLE,这里XXX被忽略,的是出现在迁移名之后的字段及其类型的清单。
rails无法判断字段的默认值,一般默认值为null,但是我们把已有的购物车的默认值设置为1。在应用迁移前,要先修改/db/migrate/20130326064705_add_quantity_to_line_items.rb
class AddQuantityToLineItems < ActiveRecord::Migration def self.up add_column :line_items, :quantity, :integer, default: 1 end def self.down remove_column :line_items, :quantity end end
2.执行迁移
Administrator@JARRY /e/works/ruby/depot (master) $ rake db:migrate == AddQuantityToLineItems: migrating ========================================= -- add_column(:line_items, :quantity, :integer, {:default=>1}) -> 0.0469s == AddQuantityToLineItems: migrated (0.0625s) ================================
3.现在Cart中需要一个聪明的add_product方法,该方法用来判断商品清单中是否已包含了想要添加的产品:如果是的话,那就增加数量;如果不是的话,就生成一个新的LineItem。修改/app/models/cart.rb
class Cart < ActiveRecord::Base has_many :line_items, dependent: :destroy def add_product(product_id) current_item = line_items.find_by_product_id(product_id) if current_item current_item.quantity += 1 else current_item = line_items.build(product_id: product_id) end current_item end end
这里调用了find_by_product_id方法,但是没有定义过。Active Record模块注意到调用未定义的方法,并且发现在其名称是以字符串find_by开始和字段名结束,于是ActiveRecord模块动态地构造了查询器方法,并将其添加到类中。
4.为了使用add_product方法,还要修改商品项目控制器的create方法
/app/controllers/line_items_controller.rb
def create @cart = current_cart product = Product.find(params[:product_id]) @line_item = @cart.line_items.add_product(product.id) respond_to do |format| if @line_item.save format.html { redirect_to @line_item.cart, notice: 'Line item was successfully created.' } format.json { render json: @line_item, status: :created, location: @line_item } else format.html { render action: "new" } format.json { render json: @line_item.errors, status: :unprocessable_entity } end end end
5.为了使用新信息,最后还需要修改show视图
/app/views/carts/show.html.erb
<h2>Your Pragmatic Cart<h2> <ul> <% @cart.line_items.each do |item| %> <li><%= item.quantity %> × <%= item.product.title %></li> <% end %> </ul>
6.再次点击add to cart 添加已买过的商品,如图:
7.
Administrator@JARRY /e/works/ruby/depot (master) $ rails generate migration combine_items_in_cart invoke active_record create db/migrate/20130326072305_combine_items_in_cart.rb
8.现在rails完全推断不出想做什么了,所以,这次完全由我们来填写self.up方法/db/migrate/20130326072305_combine_items_in_cart.rb:
def self.up # replace multiple items for a single product in a cart with a single item Cart.all.each do |cart| # count the number of each product in the cart sums = cart.line_items.group(:product_id).sum(:quantity) sums.each do |product_id, quantity| if quantity > 1 # remove individual items cart.line_items.where(product_id: product_id).delete_all # replace with a single item cart.line_items.create(product_id: product_id, quantity: quantity) end end end end
先从迭代每个购物车开始;对于每个购物车及其每个相关联的商品项目,按照字段product_id进行编组,得出各字段数量之和,计算结果将是字段product_ids和数量对的有序列表;然后迭代每一组之和,从每一个组中提取product和quantity;对于数量大于1的组,将删除与该购物车和该产品相关联的所有单个的商品项目,然后用正确数量的单行商品来替代它们。
9.应用迁移
Administrator@JARRY /e/works/ruby/depot (master) $ rake db:migrate == CombineItemsInCart: migrating ============================================= == CombineItemsInCart: migrated (0.4531s) ====================================
10.查看购物车查看结果
11.迁移的一个重要原则是每一步都是可逆的,所以,还要实现了一个self.down方法。这种方法用于查找数量大于1的商品项目:为该购物车和产品添加一个新的商品项目,一个数量增加一行,最后删除该商品项目多余的行。该操作的代码如下/db/migrate/20130326072305_combine_items_in_cart.rb:
def self.down # split items with quantity>1 into multiple items LineItem.where("quantity>1").each do |line_item| # add individual items line_item.quantity.times do LineItem.create cart_id:line_item.cart_id, product_id: line_item.product_id, quantity: 1 end # remove original item line_item.destroy end end
11.回滚迁移,并查看购物车来验证结果。
Administrator@JARRY /e/works/ruby/depot (master) $ rake db:rollback == CombineItemsInCart: reverting ============================================= == CombineItemsInCart: reverted (0.2812s) ====================================
12.重新应用迁移使用命令rake db:migrate
二、迭代E2:错误处理
有种攻击:通过传递带错误参数的请求到web应用程序。购物车的链接看起来像carts/nnn,其中nnn是内部的购物车id。感觉这个不是很好,直接在浏览器上输入这个请求,并传个字符串wibble。应用程序将出现如下错误信息:
这里暴露了太多的应用程序的信息,看上去很不专业,因此我们要使应用程序有更强的韧性。
1.上图中可以看到:
app/controllers/carts_controller.rb:16:in `show'
这里抛出了异常,即这行:
@cart = Cart.find(params[:id])
如果无法找到购物车,ActiveRecord模块会抛出一个RecordNotFound的异常,显然我们需要处理这个异常。
Rails提供了方便的处理错误和报告错误的方法。它定义了称为闪存(flash)的结构。闪存是一个桶(bucket,实际上更像个散列),当处理请求时,可以在其中存储东西。对于同一会话的下次请求,在自动地删除闪存内容之前,闪存中的内容都是有效的。
通常情况下闪存是用来收集错误信息的。在视图中可以用flash存取器方法(accessor method)来访问闪存的信息。
闪存数据存储在会话中,以使其能在请求与请求的中间被访问。
现在修改show方法来拦截无效的产品id并报告问题:
/app/controllers/carts_controller.rb
def show begin @cart = Cart.find(params[:id]) rescue ActiveRecord::RecordNotFound logger.error "Attempt to acces invalid cart #{params[:id]}" redirect_to store_url, notice: 'Invalid cart' else respond_to do |format| format.html # show.html.erb format.json { render json: @cart } end end end
2.刷新http://localhost:3000/carts/wibble,没有出现错误信息了,显示了目录网页。如图:
另外从/log/development.log可以找到Attempt to acces invalid cart wibble日志信息。
三、迭代E3:对购物车的最后加工
现在还有个问题,没有办法清空购物车。
清空购物车要在购物车中添加个链接和修改购物车控制器中的destroy方法来清理会话。
1.先从模板开始,并再次用button_to方法给页面添加个按钮:
/app/views/carts/show.html.erb
<h2>Your Pragmatic Cart<h2> <ul> <% @cart.line_items.each do |item| %> <li><%= item.quantity %> × <%= item.product.title %></li> <% end %> </ul> <%= button_to 'Empty Cart', @cart, method: :delete, confirm: 'Are you sure?' %>
2.在控制器中修改destory方法,以确保用户只是删除他自己的购物车,并在重定向到索引页面之前(带有通知消息),从会话中删除该购物车:
/app/controllers/carts_controller.rb
def destroy @cart = current_cart @cart.destroy session[:cart_id] = nil respond_to do |format| format.html { redirect_to store_url, notice: 'Yout cart is currently empty!' } format.json { head :ok } end end
3.然后更新对应的测试/test/functional/carts_controller_test.rb:
test "should destroy cart" do assert_difference('Cart.count', -1) do session[:cart_id] = @cart.id delete :destroy, id: @cart.to_param end assert_redirected_to store_path end
4.点击页面Empty Cart按钮,查看效果:
5.添加新的商品项目时,也可以删除那个自动生成的闪存消息:
/app/controllers/line_items_controller.rb
def create @cart = current_cart product = Product.find(params[:product_id]) @line_item = @cart.add_product(product.id) respond_to do |format| if @line_item.save format.html { redirect_to @line_item.cart } # here! remove the notice. format.json { render json: @line_item, status: :created, location: @line_item } else format.html { render action: "new" } format.json { render json: @line_item.errors, status: :unprocessable_entity } end end end
6.用表格来整理下购物车页面,并用css制作样式:
/app/views/carts/show.html.erb
<div class="cart_title">Your Cart</div> <table> <% @cart.line_items.each do |item| %> <tr> <td><%= item.quantity %> ×</td> <td><%= item.product.title %></td> <td class="item_price"><%= number_to_currency(item.total_price, unit: "¥") %></td> </tr> <% end %> <tr class="total_line"> <td colspan="2">Total</td> <td class="total_cell"><%= number_to_currency(@cart.total_price, unit: "¥")%></td> </tr> </table> <%= button_to 'Empty Cart', @cart, method: :delete, confirm: 'Are you sure?' %>
7.要让这个代码运行起来,要分别在LineItem和Cart模型中添加方法计算总价。
/app/models/line_item.rb:
def total_price product.price * quantity end
/app/models/cart.rb:
def total_price line_items.to_a.sum{|item| item.total_price } end
然后再在修改/app/assets/stylesheets/store.css.scss,在.store{}里面添加:
/* Styles for the cart in the main page */ .cart_title{ font: 120%; font-weight: bold; } .item_price, .total_line{ text-align: right; padding: 0 0 0 1em; } .total_line .total_cell{ font-weight: bold; border-top: 1px solid #595; }
相关推荐
Ruby on Rails Guides v2 - Ruby on Rails 4.2.5
在这个全球互联的世界中,计算机编程和 Web 应用程序开发都在迅猛发展,我很期待能为中国的开发者提供 Ruby on Rails 培训。学习英语这门世界语言是很重要的,但先通过母语学习往往会更有效果。正因为这样,当看到 ...
Ruby On Rails中文教材(PDF)
ruby on rails社区网站开发源码
Ubuntu系统ruby on rails安装 Ubuntu系统ruby on rails安装 Ubuntu系统ruby on rails安装 Ubuntu系统ruby on rails安装 Ubuntu系统ruby on rails安装 Ubuntu系统ruby on rails安装 Ubuntu系统ruby on rails安装 ...
简介 Ruby On Rails 框架自它提出之日...Rails 是一个真正彻底的 MVC(Model-View-Controller) 框架,Rails 清楚地将你的模型的代码与你的控制器的应用逻辑从 View 代码中分离出来。Rails 开发人员很少或者可能从未遇到
Ruby on Rails入门经典-例子,有很多rails工程实例。
ruby on rails对mongodb的操作ruby on rails对mongodb的操作ruby on rails对mongodb的操作ruby on rails对mongodb的操作
ruby on rails最新版 这是本人精心收集的重要软件
Ruby On Rails 官方教程,这本书讲解如何使用 Ruby on Rails 框架开发应用,以及如何把应用部署到生成环境。本书使用 Rails 默认的开发工具栈开发了一个完整的社交应用(类似 Twitter)。读完本书后你将掌握如何使用...
Windows7_Cygwin_Git_RVM_Ruby1.9.3_Rails3_MongoD B_Nginx_Unicorn_Rspec_Guard_Spork(2-Ruby on Rails3 安装配置
Ruby on Rails Web开发学习实录 内容简介: 在目前的主流web开发技术中,基于ruby语言的rails框架是做网站开发速度最快的工具。它可以达到j2ee框架开发速度的5~10倍,并且代码量也非常少。另外由于代码量的大幅度...
Ruby on Rails中文指南
Ruby on Rails的性能调优方案研究,张淼森,杨杰,Ruby on Rails 框架自它提出之日起就受到广泛关注。由于Rails框架基于MVC(Model-View-Controller) 模型,可以清楚地将模型层的代码与控制层的应
ruby on rails api方便查阅
ruby on rails 教程源码,配合原书使用
ruby on rails 开发环境包(ruby1.8.7,rails2.2.3)