Reputation: 2895
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.
P.S. 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)
Upvotes: 0
Views: 558
Reputation: 84114
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.
Upvotes: 1