【问题标题】:Why do i need to reload this CollectionProxy in the rails console but not when testing?为什么我需要在 Rails 控制台中重新加载此 CollectionProxy 而不是在测试时?
【发布时间】:2014-04-30 17:07:26
【问题描述】:

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

购物车:

class Cart < ActiveRecord::Base
  has_many :items
end

项目:

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

物品也有数量属性。

现在,我在购物车上有一个实例方法,给定商品将 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 的购买,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?

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

    • @BroiStatse 感谢您的解释!我仍然对为什么我的测试通过感到有些困惑,我认为 rspec 'change{}' 本质上就是你上面写的?我复制了你的测试,它通过了。 before_count 为 0,添加项目后为 1,但是,如果我在中间添加 2 个(不同的)项目,则为 2,如果我在其中添加 2 个相同的项目,则为 1,这些是我的结果会期待。
    • 从上面继续......添加一个新项目似乎很好,我也不必在控制台中重新加载集合代理,当我更新现有项目时会发生这种情况。
    猜你喜欢
    • 2019-05-10
    • 2011-07-15
    • 1970-01-01
    • 2021-07-24
    • 2012-06-29
    • 2022-01-21
    • 2012-08-04
    • 2013-03-23
    • 1970-01-01
    相关资源
    最近更新 更多