【问题标题】:Rails custom validate breaks my testsRails 自定义验证打破了我的测试
【发布时间】:2012-05-26 01:22:00
【问题描述】:

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

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 方法,测试将正确通过。

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

谢谢!

编辑:关于第二个问题,to_a 方法不是查询数据库而不执行求和吗?我想在数据库而不是服务器端执行计算。我正在从 .NET 和 LINQ 学习 Rails,我可以使用:

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

【问题讨论】:

    标签: ruby-on-rails ruby validation activerecord


    【解决方案1】:

    这有点复杂。首先考虑无验证情况。

    您正在呼叫line_items.find_by_product_idline_items.build。这些实际上都不会导致 line_items 关联被加载,因此当您在测试的最后一行请求cart.line_items.first 时,该行项目是从数据库中新加载的,数量 == 2。

    在第二种情况下,您的验证(在调用 Cart.create 时运行)强制 rails 尝试从数据库加载关联(此时为空)。当你构建你的 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 求和。

    【讨论】:

    • 谢谢!我自己永远也想不通。关于第二个问题,to_a 方法不是查询数据库中所有的行项目吗?我编辑了问题以便更好地解释。
    • 确实如此。您正在获取所有项目,然后使用数组的 sum 方法(您可以在任何数组上使用该方法)。 line_items.sum(:price) 进行数据库端总和​​
    • 我不能提供一个块来求和吗?我试过line_items.sum { |item| item.product_price * item.quantity },但没用。
    • 不是 SQL 版本 - 它不知道如何将其转换为 SQL。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-22
    相关资源
    最近更新 更多