欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Ruby On Rails的第一个应用(七)--更智能的购物车

程序员文章站 2022-03-02 23:53:50
...

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 %> &times; <%= item.product.title %></li>
<% end %>
</ul>
 

 

6.再次点击add to cart 添加已买过的商品,如图:

 Ruby On Rails的第一个应用(七)--更智能的购物车
            
    
    博客分类: ruby on rails railsruby on railsRuby On Rails的第一个应用depot

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.查看购物车查看结果

 Ruby On Rails的第一个应用(七)--更智能的购物车
            
    
    博客分类: ruby on rails railsruby on railsRuby On Rails的第一个应用depot

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) ====================================

Ruby On Rails的第一个应用(七)--更智能的购物车
            
    
    博客分类: ruby on rails railsruby on railsRuby On Rails的第一个应用depot

 

12.重新应用迁移使用命令rake db:migrate

 

二、迭代E2:错误处理

有种攻击:通过传递带错误参数的请求到web应用程序。购物车的链接看起来像carts/nnn,其中nnn是内部的购物车id。感觉这个不是很好,直接在浏览器上输入这个请求,并传个字符串wibble。应用程序将出现如下错误信息:

Ruby On Rails的第一个应用(七)--更智能的购物车
            
    
    博客分类: ruby on rails railsruby on railsRuby On Rails的第一个应用depot

这里暴露了太多的应用程序的信息,看上去很不专业,因此我们要使应用程序有更强的韧性。

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,没有出现错误信息了,显示了目录网页。如图:

 Ruby On Rails的第一个应用(七)--更智能的购物车
            
    
    博客分类: ruby on rails railsruby on railsRuby On Rails的第一个应用depot

另外从/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 %> &times; <%= 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按钮,查看效果:

Ruby On Rails的第一个应用(七)--更智能的购物车
            
    
    博客分类: ruby on rails railsruby on railsRuby On Rails的第一个应用depot

 Ruby On Rails的第一个应用(七)--更智能的购物车
            
    
    博客分类: ruby on rails railsruby on railsRuby On Rails的第一个应用depot

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 %> &times;</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的第一个应用(七)--更智能的购物车
            
    
    博客分类: ruby on rails railsruby on railsRuby On Rails的第一个应用depot