这两天虽然游戏玩了一会,但好歹还是写了点东西,都是边看资料边写,所以显得很混乱,本来不想记下来,又怕以后忘记,所以还是记下来好了。。。。源码还是在:
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有多少个。
到现在,一切看起来很顺利。。。。但是有两个隐患
- 如果这样关联的话,carts_products这张表,就会很大很大。如果一个cart中只有一个product,但这个product有1000个,那么,carts_products表里就会有1000行数据,明显不科学。。。
- 没有办法去一件一件的删除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();
好了,就这样吧。。。估计过段时间再来看也不大会看得懂。。。。。