Hard to find bugs Episode #23221
OK since I have not talked much about snowdevil and how its faring in production I’ll tell you about one of those really hard to find bugs and how to go about fixing it. This is also meant to give a glimpse at how maintenance works in a test driven environment using ruby on rails. I’ll show you how to create the test cases which expose the bugged behavior, how to fix it and how to restore the integrity of your database using rails facilities.
Whenever an exception is thrown on snowdevil I get an email. This has been an invaluable tool so far. This email contains
everything from requested URL to user data (if available) to stack traces, environment variables, params, session and all the rest of the anatomy of the request known to the system.
Yesterday I started getting very odd emails. A user was adding items to his shopping cart but he would get an exception in doing so.
Now this is a fairly well tested concept in this store ( We sold 10 snowboards just last week). So what went wrong?
Turns out that this particular user is a returning customer and on his last visit he left the store with items in his shopping cart which are since deleted. This leads to a “undefined method ‘product’ for nil” type error when the shopping cart is supposed to be displayed. Ouch!
Bugs stink so lets fix this critter.
First lets recreate the issue in a test case. The bug happens when rendering the shopping page so we will go with a functional test case first.
Note that we won’t fix the error in the template, even though this sounds appealing. The error coming from ActionView is only a symptom. We will
fix the cause.
def test_user_with_deleted_item_in_cart
# add item one to cart
post :add, "id" => 1
# delete this item
Product.find(1).destroy
assert_raise(ActiveRecord::RecordNotFound) do
Product.find(1)
end
assert_raise(ActiveRecord::RecordNotFound) do
ProductUnit.find(1)
end
# add another item to cart
post :add, "id" => 3
# tell the cart to refresh its items collection. This is done
# automatically between real requests
@request.session["cart"].items(:reload)
# render the cart overview
get :index
assert_success
end
BAM! ActionView::TemplateError: undefined method `product’ for nil:NilClass. Ever been happy about getting an error? Here is your chance!
This is exactly what I have been emailed about by my trusty store.
So now lets fix it, ok? No we do TDD. Lets declare our intention with another unit test:
def test_deleted_unit
@board1_151cm.destroy
assert_raise(ActiveRecord::RecordNotFound) do
@cart1_item1.reload
end
end
Wonderful. This fails as well.
Now equipped with our failing unit tests we can go and write some production code to fix the issue:
class ProductUnit < ActiveRecord::Base
has_many :cart_items, :foreign_key => "unit_id", :dependent => true
[...]
end
rake tells us that all unit tests work and we fixed a bug. forever and it will never appear again or rake would tell us.
Now off to deploying the changes on the production site and fixing the database retroactively.
tobi@commerce snowdevil $ ruby script/console production
Loading environment...
irb(main):001:0> CartItem.find_all.each { |o| o.destroy if o.unit == nil }
So long and thanks for all the fish.
If you are interested to learn about TDD i recommend the book Test driven development