This post originated from an RSS feed registered with Ruby Buzz
by Tobias Luetke.
Original Post: Hard to find bugs Episode #23221
Feed Title: too-biased
Feed URL: http://blog.leetsoft.com/xml/rss/
Feed Description: Read the announcement: CD Baby rewrite in Postgres and Ruby, Baby!CD Baby is going rails. This is huge! If you don’t know CD Baby, its a distribution site with over 80.000 musicians under contract and its one of the biggest digital distributors of audio to Apple iTunes, Napster, Rhapsody, etc. Read all about it here. What is even more exciting about it is that I’m on board for the rewrite and I had chance to talk to Derek on the phone directly for a consulting session which might have played its little part in the decision to go with rails. Come monday I’ll work together with him and other rails contributor Jeremy Kemper to help crunch those 90k lines of PHP in beautifully compact code for which Rails and Ruby are known. Here is a quote from Derek’s announcement on his weblog: Like a lost soul walkin’ the earth, lookin’ for spirituality, that stumbles upon the right church with the right people at the right time, I’ve found my niche with Ruby. Its little itty-bitty community attracts some brilliant “think different” types with a love for beautiful code that do this for love, not money.
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 }