首页 > Web开发, 动态语言, 挨踢(IT) > 《Agile Web Development with Rails》抄书笔记(11):优化购物车

《Agile Web Development with Rails》抄书笔记(11):优化购物车

2013年6月12日 发表评论 阅读评论 311 人阅读    

《Agile Web Development with Rails》抄书笔记系列

  “《Agile Web Development with Rails》抄书笔记系列”目录

  虽然上一节,我们创建了个一个初步在购物车,也通过了功能性验证。但是,这个购物车还有很多不完善的地方,比如,如果顾客添加了多个同一件商品,那么我们就需要重组这个购物车。这一节,我们将从这方面着手,来完善这个购物车。

D呱呱

  关于这节内容的代码:

  1. 这节开始之前:https://github.com/diguage/depot/tree/v-09.3
  2. 这节完成之后:https://github.com/diguage/depot/tree/v-10.1

  解决上面提到的这个问题,其实很简单只需要给 line_items表增加一个表示数量的列即可,我们将这一列命名为quantity,数据类型为整形。这时就需要修改数据库的表结构。我们在前面用到了这样的相应指令(内部实现是一个脚本),那么我们可以使用类型的命令来完成这项工作。命令如下:

rails generate migration add_quantity_to_line_items quantity:integer

  这个指令的意思是向某个表增加一到多列,列的名称和数据类型从这个指令的最后一部分参数来获取。与add_XXX_to_TABLE向对应的,Rails还提供了remove_XXX_from_TABLE方法,这两个用于向表TABLE中添加或者删除列。经过D瓜哥的多次测试后发现,这里的XXX只是一个名字或者说标识符,并不一定和表中的列相同;但是TABLE必须和相应的表名相同,否则就会报错。另外,这两个参数后必须跟相应的列名和数据类型才行,否则不产生任何作用。

  一般情况下,我们向购物车中添加物品,默认是添加一件。所以,我们来修改迁移脚本,来设置表中列的默认值为1。打开%Depot%/db/migrate/20130316075458_add_quantity_to_line_items.rb文件,将其按照以下代码来修改:

class AddQuantityToLineItems < ActiveRecord::Migration
  def change
    add_column :line_items, :quantity, :integer, default: 1
  end
end

  这时,执行迁移命令,来完成迁移:

rake db:migrate

  执行完成后,检查一下line_items表是不是多了一列?

  修改完数据库表结构后,我们就需要在Cart中增加一个方法add_product(),来实现添加商品的功能:如果购物车中没有这件商品,则添加一件商品到购物车中;如果购物车中已有这件商品,则增加商品数量。打开%Depot%/app/models/cart.rb文件,增加如下方法:

  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

  也许大家会纳闷,我们并没有定义第二行中出现的find_by_product_id方法,但是为什么却可以使用呢?Active Record注意到这里有一个以find_by开头,以表中某列结尾的方法,则它会动态定义这样一个方法,添加到我们的类中。关于这块的内容,以后还会再深入讲解。

  既然我们在Model中添加了相应的方法,则Controller可以使用Model的对象直接调用这个方法。所以,我们也需要对Controller做一些稍微的改动。打开%Depot%/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,
               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

  然后,我们来修改一下购物车的展示页面,把每种商品的数量展示出来。打开%Depot%/app/views/carts/show.html.erb文件,修改如下:

<% if notice %>
<p id="notice"><%= notice %></p>
<% end %>

<h2>Your Pragmatic Cart</h2>

<ul>
  <% @cart.line_items.each do |item| %>
       <li><%= item.quantity %>&times;<%= item.product.title %></li>
  <% end %>
</ul>

  这时,大家打开http://127.0.0.1:3000/,打开添加按钮,向购物车中添加一些东西,看看效果。有木有发现,如果以前添加过多个相同的商品,则同一件商品会有多个显示。看来,我们有必要对以前的数据做一次迁移。执行如下命令,添加一个数据迁移脚本:

rails generate migration combine_items_in_cart

  打开产生的迁移脚本%Depot%/db/migrate/20110711000005_combine_items_in_cart.rb(由于时间戳的原因,您看到的文件名和我列出的并不一定完全一致),对其修改如下:

  def 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

  这时,执行迁移指令:

rake db:migrate

  是不是报错了?

F:\depot>rake db:migrate
==  CombineItemsInCart: migrating =============================================
rake aborted!
An error has occurred, this and all later migrations canceled:

Can't mass-assign protected attributes: quantity
C:in `create'
F:/depot/db/migrate/20130316082624_combine_items_in_cart.rb:13:in `block (2 levels) in up'
F:/depot/db/migrate/20130316082624_combine_items_in_cart.rb:7:in `each'
F:/depot/db/migrate/20130316082624_combine_items_in_cart.rb:7:in `block in up'
F:/depot/db/migrate/20130316082624_combine_items_in_cart.rb:4:in `each'
F:/depot/db/migrate/20130316082624_combine_items_in_cart.rb:4:in `up'
C:in `migrate'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)

  解决方法是,在%Depot%/app/models/line_item.rb文件中增加如下一行:

attr_accessible :quantity

D呱呱

  关于这个问题,D瓜哥在上一节“《Agile Web Development with Rails》抄书笔记(10):创建购物车”中已经解释过了,这里就不再赘述了。

  这时,再运行迁移指令:

rake db:migrate

  应该就OK了。然后刷新购物车页面或者从首页添加商品然后调到购物车页面,相同的商品就在一列中显示出来了。

  另外,还要给大家提醒一点,迁移的一个重要原则是每一步都必须可逆的。所以,上一步的合并也不例外。为了实现上一步合并的反响操作,我们就必须实现down()方法,查询出line_items表中quantity>1的记录,然后逐个将其插入到line_items表中;然后再把刚才查出来的记录删除掉。打开文件%Depot%/db/migrate/20130316082624_combine_items_in_cart.rb,修改代码如下:

  def 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

  然后执行如下命令来实现”回退”:

rake db:rollback

  由于我们就是为了实现合并,所以这条指令就不实际执行了。

  到这里,我们这节需要讲解的东西都已经说完了。给大家点悬念:大家觉得目前这个购物车的实现是否尽善尽美?是否还有可以改进的地方?这一点,到下一节来揭晓。

D呱呱

使用脚本添加列,但是还没运行迁移指令时,报错:

NoMethodError in Carts#show

Showing F:/depot/app/views/carts/show.html.erb where line #9 raised:

undefined method `quantity' for #<LineItem:0x3308490>

Extracted source (around line #9):

<ul>
  <% @cart.line_items.each do |item| %>
      <li><%= item.quantity %>&times;<%= item.product.title %></li>
  <% end %>
</ul>


作 者: D瓜哥,https://www.diguage.com/
原文链接:https://www.diguage.com/archives/17.html
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.