我一直在rails中创建购物车功能,我有以下模型:
购物车:
class Cart < ActiveRecord::Base
has_many :items
end
单品:
class Item < ActiveRecord::Base
belongs_to :cart
belongs_to :product
end
一个项目也有一个quantity属性。
现在,我在购物车上有一个实例方法,给定一个商品,它将a)将该商品保存到数据库并将其与购物车关联,或者b)如果具有product_id的商品已经存在,则只需更新数量。
代码如下:
def add_item(item)
if(item.valid?)
current_item = self.items.find_by(product_id: item.product.id)
if(current_item)
current_item.update(quantity: current_item.quantity += item.quantity.to_i)
else
self.items << item
end
self.save
end
end
这个很好。
然而,我想在控制台中测试这一点,所以我在沙盒模式下打开控制台并运行以下命令:
cart = Cart.new #create a cart
cart.add_item(Item.new(product: Product.find(1), quantity: 5)) #Add 5 x Product 1
cart.items #lists items, I can see 5 x Product 1 at this point.
cart.add_item(Item.new(product: Product.find(1), quantity: 3)) #Add 3 x Product 1
cart.items #lists items, still shows 5 x Product 1, not 8x
cart.items.reload #reload collectionproxy
cart.items #lists items, now with 8x Product 1
这里我创建了一个购物车,添加了5 x产品1的购买,我可以在cart.items中看到这一点。如果然后添加另一个购买3 x产品1,购物车。items仍然将购买列为5 x Product 1,直到我手动重新加载收集代理。
我可以添加更多的产品,这些会显示出来,只是当更新一个现有的产品时,它不会更新集合。
我也有关于这个方法的测试,这些测试都通过了。
before(:each) do
@cart = create(:cart)
@product = create(:product)
@item = build(:item, product: @product, quantity: 2)
end
context "when the product already exists in the cart" do
before(:each) {@cart.add_item(@item)}
it "does not add another item to the database" do
expect{@cart.add_item(@item)}.not_to change{Item.count}
end
it "does not add another item to the cart" do
expect{@cart.add_item(@item)}.not_to change{@cart.items.count}
end
it "updates the quantity of the existing item" do
@cart.add_item(@item)
expect(@cart.items.first.quantity).to eq 4
end
end
context "when a valid item is given" do
it "adds the item to the database" do
expect{@cart.add_item(@item)}.to change{CartItem.count}.by(1)
end
it "adds the item to the cart" do
expect{@cart.add_item(@item)}.to change{@cart.items.size}.by(1)
end
end
我想知道的是,当我在控制台中使用此方法时,为什么我必须重新加载CollectionProxy ?
Association缓存查询结果以获得更好的性能。当您第一次调用@cart.item
时,它将调用db来获取与给定购物车相关的所有项目,并且它将记住它的输出(在称为'target'的内部变量中),因此每次您在初始调用之后调用它,它都会给您相同的结果,而根本不调用db。强制它再次进入db的唯一方法是清除目标变量-这可以通过reload
方法或将true
传递给关联调用@car.items(true)
来完成。
你不需要在你的rspec测试中重新加载关联的原因是因为你没有在任何对象上两次调用items
。但是,如果您编写这样的测试:
it 'adds an item if it is not in the cart' do
before_count = @cart.items.size # size forces the association db call
@cart.add build(:item)
after_count = @cart.items.size # items has been fetched from db, so it will return same set of results
after_count.should_not eq before_count
end
该测试将失败,因为您在同一对象上调用了两次items
-因此您将得到相同的结果。请注意,使用count
而不是size
将使该测试通过,因为count
正在更改SQL查询本身(其结果没有被缓存),而size
被委托给关联target
对象。