On my site, if a lot of people place orders at the same time, the
available quantity left in stock for a product seems to get wrong
every so often.
Here’s my execute_purchase method, which is a before_filter on the
Order model:
def execute_purchase
Product.transaction do
product.lock!
if product.quantity >= quantity # enough in stock
if credit_card_needed?
response = authorize_payment
if response.success?
product.update_attribute(:quantity, product.quantity -
self.quantity)
else
raise FulfillmentError, response.message
end
end
else
raise FulfillmentError, “not enough in stock”
end
end
rescue FulfillmentError => e
errors.add_to_base e.message
return false
end
So, I enter the transaction. Lock the product. I see if there’s
enough in stock. If there’s enough in stock, then I authorize the
payment – if that’s successful, then I decrement the available
quantity. Since I’m in a transaction and I’ve locked the record,
this method should be fine, right?
So, what happens is sometimes we sell more than we have. So, the
product quantity isn’t always updated when an order happens.
Shouldn’t the
product.lock!
line give me a lock on that row? So, the other requests need to wait
until the transaction is over with. Then the product should get
reloaded with current data.
Sorry for the mess in the previous message, cut and paste error.
Basically when you instantiate an AR object it loads the data at that
moment and never updates until you call the .reload method.
An AR object is not aware of what happens to its image in the database.
Here’s my execute_purchase method, which is a before_filter on the
Order model:
def execute_purchase
Product.transaction do
if product.quantity >= quantity # enough in stock
if credit_card_needed?
UPDATE products SET quantity=quantity-#{product.quantity}
response = authorize_payment
if response.success?
msg Success
else
UPDATE products SET quantity=quantity+#{product.quantity}
raise FulfillmentError, response.message
end
else # what if credit card is not needed ??
end
else
raise FulfillmentError, "not enough in stock"
end
end
rescue FulfillmentError => e
errors.add_to_base e.message
return false
end
Imho no lock should be necessary in this case.
You simply risk to answer that there’s no more stock whereas there is
some 5 ms later.
Better than the opposite.