ROR学习笔记(41)——简陋的购物车功能

这两天虽然游戏玩了一会,但好歹还是写了点东西,都是边看资料边写,所以显得很混乱,本来不想记下来,又怕以后忘记,所以还是记下来好了。。。。源码还是在:
https://github.com/kamionayuki/shop
已经有了products表了,所以只需要创建一个carts表就OK了,又因为每一个用户有且只有一个购物车,所以carts表除了id列,就只有一列user_id。而User这个模型又devise生成(具体不就多说了)这样,就有了products和carts两张表。购物车的话,应该是多对多关系,一个cart可以有多个prodcuct,而一个product也可以存在多个cart中。所以最开始想到了用 has_and_belongs_to_many 来实现 多对多的模型关联。所以创建了一张表:carts_products 只有两列:cart_id 和 product_id然后就可以用关联生成的方法cart.products.size来查询cart中有多少product。也可以通过代码来排重,查询有多少种product,甚至可以查询每一种product有多少个。
到现在,一切看起来很顺利。。。。但是有两个隐患

  1. 如果这样关联的话,carts_products这张表,就会很大很大。如果一个cart中只有一个product,但这个product有1000个,那么,carts_products表里就会有1000行数据,明显不科学。。。
  2. 没有办法去一件一件的删除cart中的product。如果一个product有10件,用delete和destroy都是直接把这10件都删除掉了。也不科学。。。
    发愁了,想到了增加product_number这一列来记录某一类product有多少个。但是怎么去操作呢?只能找资料了。。。看了一下,似乎 has_many 好像可以满足。于是
rails g model CartRelationship cart_id:integer product_id:integer product_number:integer

生成一个中间模型,同时用carts_relationships 这张表来做中间表进行操作。model中的代码如下:
app/models/cart.rb

has_many :cart_relationships
has_many :products, through: :cart_relationships

app/models/product.rb

has_many :cart_relationships
has_many :carts, through: :cart_relationships

app/models/cart_relationship.rb

belongs_to :cart
belongs_to :product

这样就关联好了。但还是一样,没有办法去一件一件的删除cart中的product。并且用cart.products << @product的时候,也不会对product_number这一列进行操作。所以只能自己在cart.rb中写add和delete的方法了
具体代码如下:

def delete_from_cart(product)
    result = select_cart_relationship(product)
    if 1 == result.product_number
      self.products.destroy(product)
    else
      result.product_number -= 1
      result.save
    end
end

def add_to_cart(product)        
    if self.products.exists?(product) 
      result = select_cart_relationship(product)     
      result.product_number += 1      
    else
      self.products << product
      result = select_cart_relationship(product)
      result.product_number = 1
    end
    result.save
end

def select_cart_relationship(product)
    query = "select * from cart_relationships where cart_id = %d && product_id = %d" % [self.id, product.id]
    temp = CartRelationship.find_by_sql(query).first
end

其中的关键在 select_cart_relationship 这个方法。通过find_by_sql的方法进行查询,得到一个数组,然后取第一个元素。
它返回的值是一个CartRelationship的实例,这个实例有id,cart_id,product_id以及product_number的属性。
因此,我们就可以对Product_number进行更新和保存。逻辑直接看代码就OK了。

至此,我们可以通过cart.products来得到cart中拥有的product的种类
然后通过 select_cart_relationship(product).product_number这个方法来得到某一类product有多少个。所以我们也可以用以下的代码来得到这个cart中所有的product的个数

@cart.products.inject(0) do |result, p|
    result += @cart.select_cart_raletionship(p).product_number 
end

但是看后台数据库的执行,如果cart中有100类product,那么就会执行100次查询,虽然不知道性能会怎么样,但看起来太吓人了,所以又开始查资料,最后用下面的方法实现了:

def product_count
    sql = ActiveRecord::Base.connection() 
    query = "select sum(product_number) sum from cart_relationships group by cart_id having cart_id=%d" % self.id
    sql.exec_query(query).sum["sum"]
end

具体里的的逻辑不太了解,但从表面上来看:是先实例化了一个数据库链接的实例
然后通过这个实例调用exec_query来直接执行mysql语句。
tips:可以先把mysql语句在数据库中执行一下,看是否正确
这样每查询一次cart中所有的product的个数,只需要执行一次查询就OK了。但由于是边看资料边写的,所以每调用一次product_count方法,就需要实例一个数据库链接的实例,可能也是蛮耗资源的?总之,数据库操作是实现了。
那么controller和views中就好办了。只需要写逻辑就可以了
*controller中,购物车肯定是用ajax更好,所以还是继续ajax。

 class CartsController < ApplicationController
  before_action :authenticate_user!, only: [:show]
  def show
    @cart = current_user.cart
  end

  def add_to_cart
    if !current_user.nil?
      @product = Product.find(params[:product_id])
      @cart = current_user.cart
      @cart.add_to_cart(@product)
      @total_count = @cart.product_count
      @product_count = @cart.select_cart_relationship(@product).product_number
      flash.now[:notice] = "成功添加1件#{@product.name}到购物车"
      render 'change_cart'
    else
      flash[:notice] = 'please sign in first.'
      flash.keep(:notice)
      render js: "window.location = '/users/sign_in'"
    end
  end

  def delete_from_cart
    if !current_user.nil?
      @product = Product.find(params[:product_id])
      @cart = current_user.cart
      @cart.delete_from_cart(@product)
      @total_count = current_user.cart.product_count
      if @cart.products.exists?(@product)
        @product_count = @cart.select_cart_relationship(@product).product_number 
        flash.now[:notice] = "已经删除1件#{@product.name}"
        render 'change_cart'
      else
        flash.now[:notice] = "删除了最后一件#{@product.name}"
        render 'delete_from_cart'
      end
    else
      flash[:notice] = 'please sign in first.'
      flash.keep(:notice)
      render js: "window.location = '/users/sign_in'"
    end
  end
end

views中(其中还学到了在js.erb文件中,<%= render text%>可以嵌入在 "" 字符串中的任意位置):
change_cart.js.erb

var total_count = "Cart(<%= render text: @total_count %>)";
var summary = "共<%= render text: @cart.product_count %>件物品,总计<%= render text: @cart.total_summary %>";
//更新页面上总个数和总价显示
$(".cart_count").text(total_count);
$(".summary").text(summary);
//更新提示
$("#sidebar_nav").next().remove();
$("#sidebar_nav").after("<%= j render('layouts/flash') %>");
//更新列表中某一类product的个数
var xx = $("a[href$='add_to_cart/<%= render text: @product.id %>']");
xx.prev().text("<%= render text: @product_count %>");

delete_from_cart.js.erb

//与change_cart.js.erb类似,只是把数量为0的product移除不显示
var total_count = "Cart(<%= render text: @total_count %>)";
var summary = "共<%= render text: @cart.product_count %>件物品,总计<%= render text: @cart.total_summary %>";
//更新页面上总个数和总价显示
$(".cart_count").text(total_count);
$(".summary").text(summary);
//更新提示
$("#sidebar_nav").next().remove();
$("#sidebar_nav").after("<%= j render('layouts/flash') %>");
//移除数量为0的product的显示
var xx = $("a[href$='add_to_cart/<%= render text: @product.id %>']");
xx.parent().parent().remove();

好了,就这样吧。。。估计过段时间再来看也不大会看得懂。。。。。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容