为什么我需要在rails控制台中重新加载这个CollectionProxy,而不是在测试时?

时间:2014-03-23 12:21:03

标签: ruby-on-rails ruby rspec rspec-rails rails-console

我一直在rails中创建购物车功能,我有以下型号:

购物车:

class Cart < ActiveRecord::Base
  has_many :items
end

档案:

class Item < ActiveRecord::Base
  belongs_to :cart
  belongs_to :product
end

项目也有数量属性。

现在,我在购物车上有一个实例方法,给出一个项目将a)将项目保存到数据库并将其与购物车相关联,或b)如果具有product_id的项目已经存在,只需更新数量。 / p>

以下代码如下:

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,则cart.items仍会将购买列为5 x产品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?

1 个答案:

答案 0 :(得分:2)

关联缓存查询结果以获得更好的性能。当你第一次调用@cart.item时,它将调用数据库来获取与给定购物车相关的所有项目,并且它将记住它的输出(在内部变量中称为“目标”),因此每次在此之后调用它初始调用它会给你相同的结果而不需要调用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对象。