Don't Extend Nil · 407 days ago
If you are using Ruby and you have fully embraced the power of include and extend, make sure that you never accidentally extend a class that you didn’t intend, ahem, like the nil singleton.
So one of my colleagues was having this really weird bug. The home page of a rails site downloads an rss feed parses it and creates html from it. This gets updated every six hours, but in development it reloads every request (rereads a cached xml file in tmp). But after logging into the member’s section the items array in the RSS object was empty.
So I wrote some integration tests to recreate it, it will not break in test. So I begin the painful endeavor of testing by hand, over and over, for days. Altering, removing random pieces of code involved in the parsing of the rss feed and the login system. Can’t track it down.
Aha! I found it! If I remove scaffold from the member’s home page the bug disappears. There must be a bug in scaffolding extension’s scaffold method. So I start deconstructing the scaffold method. Further and further into the bowels of scaffolding extensions and I find that it is calling a method ActiveRecord::Base.all_models. That’s it. There’s the culprit, right?
nope
What was really happening. I wrote a plugin that allows adding extra data to ActiveRecord’s column objects. This comes in hand for creating forms. That column will always have the same information for displaying in forms. No matter where it is placed, my helpers know how to handle it.
The real bug was here:
columns_hash[key.to_sym] = column.extend(ColumnDataExtension)
Well if you have misnamed one of these columns (like I did) then columns_hash[key.to_sym] returns nil, and nil gets extended. Weirdness ensues. Time is lost. Baby ducks will die. It is horrible, don’t ever extend without checking nil? first.
The bug only happened after loading a particular class. Which was occurring after a call to scaffold which called ActiveRecord::Base.all_models which loaded the bad model. It did not raise errors but changed how nil functioned. The thing I still can’t figure out is why this was working fine in test but not in development or production.