When good metaprogramming goes bad
So you’re cruising along, minding your own business, writing your Ruby based application when you hit a bug. A few seconds/minutes/hours later you’ve figured out what this bug is and you have an idea of what is causing it. Ok, this is easy, Ruby apps have all their code open, you can just go in, find the offending method and fix it up. This is so easy you’ll probably even submit a patch back to the project, dang you’re a nice developer
Several hours later you’re ready to toss the library, Ruby, your computer and that guy who drank the last Dr. Pepper. Yes, you have fallen victim to the scourge that is bad metaprogramming. Now I’m not talking about deliberately nasty, obfuscated Ruby type bad metaprogramming. It’s more like well meaning used-to-be-good metaprogramming that’s just gone over the edge and doesn’t realize it.
So what does bad metaprogramming look like? Well, I don’t have a great litany of the greatest offenders, but I do have a delicious little gem from our beloved (or not so beloved depending on your perspective), Active Record. You see, I was trying to figure out why my Derby queries were asploding when I used a range in my conditions. (Derby is a pure Java database that I’m hitting via JRuby and AR-JDBC). I found out that Derby requires the time portion of the date, even though their docs would indicate that just a date will work. Also, the SQL output of a DateTime object is very wrong for what Derby is expecting. So, of course the problem is in the Derby adapter, I’ll just check into that and fix up the few places where it’s not outputting the correct SQL.
Derby wants: ‘2008-06-04 00.00.00’ or ‘2008-06-04-00.00.00’
Derby is getting: ‘2008-06-04’ for Date objects and ‘2008-06-04T14:22:50-07:00’ for DateTime object
I dig around a bit and find out that my conditions (in this case a range object) quote_bound_value method which in turn calls the quote method of whatever connection is being used. I’m using JRuby so my connection is of type ActiveRecord::ConnectionAdapters::JdbcAdapter. Ok, great, I dive into that class and search for the quote method. Well, JdbcAdapter doesn’t have such a method, but it’s base class AbstractAdapter does mix in the Quoting mixin which… has a quote method! All is well in the world, except that the quote method of the Quoting mixin isn’t actually being called. I can remove the include statement from AbstractAdapter and I still get the same SQL output. Sigh. Ok, well one of the other mixins to JdbcAdapter must have a quote method that’s overriding the one on Quoting, surely. Well iterating through everything in JdbcAdapter’s ancestors and listing their instance methods yields exactly zero instances of a quote method. Now at this point we enter the realm of eval, instance_eval and singeleton methods, any of which might be used here and all of which are pretty darn difficult to track down. My regex-fu failed and I was unable to find the actual source of the quoting.
A coworker recommended that since I know that Date is getting to_string’d that I stub out that method and have it print out the stack. Great idea. So I reopen Date, do a little open heart surgery on to_s and get my stack trace. It’s a great stack trace, except that it leaves off at quote_bound_value. So evidently Ruby’s having an issue keeping track of where the next method lives also. Oh well, it was a nice try.
In the end, having seen how Active Record resolves its parameters I developed a workaround. I pass a range of string values, of which Active Record will call .first and .last to get the values to use for the between part of the query. So by pre-formatting my dates I get a valid query.
I had heard of the “black magic” properties of Active Record, people commenting on how it was doing too much metaprogramming. Up to this point I mostly figured those people were from the Java or PHP camps and simply didn’t like metaprogramming in general. I know feel their pain. So remember, when writing your libraries, considerhow hard it’s going to be for someone to come along and track down a bug in your system so that they can submit a patch.
- An error has occurred; the feed is probably down. Try again later.
- @kylepulver Touche 15 hours ago
- @kylepulver Look at the bright side Kyle, now there's nowhere to go but pu. 15 hours ago
- RT @sean_a_rose: Writing is hard because it often ends up being the process through which you realize that you don't actually understand so… 20 hours ago
- RT @alisongriswold: Sine game 💯 https://t.co/S81UrJpY24 1 day ago
- RT @FullFrontalSamB: Fixed. https://t.co/pKUHKzJ3Vz 1 day ago