简体   繁体   中英

Rails custom validate breaks my tests

I have the following cart model which contains many line items. It contains only one method which adds a new line item and increment its quantity.

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

The line item model is the following:

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

  def total_price
    product.price * quantity
  end
end

The following test was working correctly:

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

All went well until I added the third line validate :cart_total_price_cannot_be_greater_than_500 . This is now breaking my tests and i get the following error from 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>.

What I am doing wrong? If I comment out the validate method, tests will pass correctly.

PS my second question is: why if I don't add the "to_a" method before calling sum on the cart_total_price method it does not work?

Thanks!

EDIT: about the second matter, isn't the to_a method querying the database without performing the sum? I would like to perform the calculation on the db rather than server-side. I am learning Rails from .NET and in LINQ I could have used:

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

This is a little intricate. First consider the no validation case.

You're calling line_items.find_by_product_id and line_items.build . None of this actually causes the line_items association to be loaded, so when in the last line of your test you ask for cart.line_items.first the line item is loaded fresh from the db, with quantity == 2.

In the second case your validation (which runs when Cart.create is called) forces rails to try and load the association from the db (empty at this point). When you build your line_item, this built object gets added to the cache of the loaded association (quantity == 1)

You then add the product a second time. line_items.find_by_product_id fetches the product from the db. Since activerecord has no identity map, this is actually a separate ruby object from the line item held in the cache (albeit one that refers to the same database object). That (now stale) object still has quantity == 1, although the row in the database has a quantity of 2.

When you ask for cart.line_items.first rails sees that it has already got that association loaded and so gives you back the cached line item object from the first call to add_product, which has the stale quantity value. Thus your assertion fails.

You could make your spec pass by calling cart.reload after you've added the products.

To answer to your second question it's because the to_a results in Array#sum being called whereas without it rails things you want to do an SQL sum over the line items which requires a different set of arguments.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM