Rails自定义验证会破坏我的测试

时间:2012-05-24 21:50:35

标签: ruby-on-rails ruby validation activerecord

我有以下购物车型号,其中包含许多订单项。它只包含一个添加新订单项并增加其数量的方法。

class Cart < ActiveRecord::Base
  has_many :line_items, dependent: :destroy
  validate :cart_total_price_cannot_be_greater_than_500

  def add_product(product_id)
    current_item = line_items.find_by_product_id(product_id)

    # Updates quantity or add a new line item
    if current_item
      current_item.quantity += 1
    else
      current_item = line_items.build(product_id: product_id)
              current_item.quantity = 1
      product = Product.find(product_id)
      current_item.product_price = product.price
    end

    current_item
  end

  def cart_total_price
    line_items.to_a.sum { |item| item.product_price * item.quantity }
  end

  def cart_total_price_cannot_be_greater_than_500
    if cart_total_price > 500 
      errors.add(:base, "has a too high price") 
    end
  end
end

订单项模型如下:

class LineItem < ActiveRecord::Base
  belongs_to :product
  belongs_to :cart

  def total_price
    product.price * quantity
  end
end

以下测试工作正常:

require 'test_helper'

class CartTest < ActiveSupport::TestCase
  fixtures :products

  test "add duplicated products to cart" do
    cart = Cart.create

    # :ruby is a product
    cart.add_product(products(:ruby).id).save
    cart.add_product(products(:ruby).id).save

    assert_equal 1, cart.line_items.size
    assert_equal 2, cart.line_items.first.quantity
  end
end

一切顺利,直到我添加第三行validate :cart_total_price_cannot_be_greater_than_500。这现在打破了我的测试,我从rake test得到以下错误:

Finished tests in 0.294143s, 23.7979 tests/s, 84.9927 assertions/s.

  1) Failure:
test_add_duplicated_products_to_cart(CartTest) [/home/luca/Documents/Sites/depot/test/unit/cart_test.rb:14]:
<2> expected but was
<1>.

我做错了什么?如果我注释掉validate方法,测试将正确传递。

P.S。我的第二个问题是:如果我在cart_total_price方法上调用sum之前没有添加“to_a”方法,为什么它不起作用?

谢谢!

编辑:关于第二件事,是不是在没有执行总和的情况下查询数据库的to_a方法?我想在db而不是服务器端执行计算。我正在学习.NET中的Rails,在LINQ中我可以使用:

int sum = dbContext.LineItems.Where(l => l.CartId == cartId).Sum(l => l.Quantity * l.ProductPrice)

1 个答案:

答案 0 :(得分:1)

这有点错综复杂。首先考虑无验证案例。

您正在呼叫line_items.find_by_product_idline_items.build。这些都不会导致加载line_items关联,因此当您在测试的最后一行中要求cart.line_items.first时,新订单中的订单项是新填充的,数量== 2.

在第二种情况下,您的验证(在调用Cart.create时运行)会强制rails尝试从db加载关联(此时为空)。构建line_item时,此构建对象将添加到已加载关联的缓存中(数量== 1)

然后再次添加产品。 line_items.find_by_product_id从数据库中获取产品。由于activerecord没有标识映射,因此这实际上是与缓存中保存的行项目分开的ruby对象(尽管是指向同一数据库对象的行项目)。尽管数据库中的行的数量为2,但该(现在陈旧的)对象仍具有数量== 1。

当您要求cart.line_items.first rails看到它已经加载了该关联时,因此从第一次调用add_product返回缓存的行项目对象,该对象具有过时的数量值。因此你的断言失败了。

您可以在添加产品后致电cart.reload来传递规范。

回答你的第二个问题是因为to_a会导致调用Array#sum,而没有它会阻止你想要对需要不同参数集的行项进行SQL求和。