Tuesday, February 12, 2008

I'm Proud Not To Understand has_many :through

Here's why:

I went into the gem directory for has_many :through, and used ack - a superior alternative to grep - to find every "class FooError" line in the file associations.rb, which defines has_many :through and all its friends.

Of the nine error classes defined by the entire associations system in Rails, seven of them deal with has_many :through.

My instinct before this discovery was to avoid has_many :through whenever I could. Having made this discovery, I really have to say, avoid has_many :through whenever you can. What used to be casual laziness on my part has morphed into deliberate strategy without changing in any important practical aspect. has_many :through error classes outnumber all other error classes in Rails' associations internals combined by a factor of more than three to one. That's a sure sign that code needs refactoring.

I wish I knew the refactoring to make here, ideally well enough to implement it and patch Rails, or even just enough to suggest a solution, but I don't. I do feel very confident that this code will be refactored at some point, because the Rails community and core team are diligent, skilled, and kinetic, but until that time, I'm going to avoid building my house on sand.

In a situation like this, learning has_many :through, unless absolutely necessary, reeks of an inability to make strategic decisions about what you learn and when. I developed this ignorance through being lazy, but maybe that's a virtue. Maybe Larry Wall was right. Certainly, if you're hiring Rails programmers, and a candidate says "I know the associations internals inside-out, except for has_many :through," that shouldn't be interpreted as almost being an expert in Rails' associations internals. That should be interpreted as not only being an expert in Rails' associations internals, but also having the good judgement to recognize when a subject is not worth obtaining expertise in.

I'm not saying learning has_many :through makes you an idiot. Any time you have knowledge somebody else needs, that's a good thing for both of you. But during the period after the dot-com crash, I tried two tactics to increase the amount of money I could charge for my programming services. One of them failed spectacularly, and one of them succeeded wonderfully. The failure was learning the skills that had the most job posts; the success was learning the skills that most engaged my own curiousity. I learned Java and got nothing out of it; I studied Bayesian filtering for fun and did consulting work in Bayesian filtering through my activity on a Ruby mailing list.

In a sense, my laziness around has_many :through was again prompted by a lack of curiousity; it just looked like too much trouble to bother with. The moral here is that if something seems boring, skip it. Trust your intuition. It's there for a reason.

Update: I don't do comments, but I've known Jack Nutting online since the early days of Schwa, and he says:

g-man, you are totally wrong on this! I've been using has_many :through extensively, and I've never encountered any of those errors, not once. I haven't looked at the code, but I'm guessing that those error conditions are there to warn people if they're using it in the wrong situation (anywhere that isn't basically a many-to-many modeled with an entity table in the middle [the "through" table] that has foreign keys to the other two). If so, it makes sense that it warns of exponentially more kinds of problems, since the fact that it uses 3 tables instead of 2 means that there are exponenentially more ways you can screw it up by using it in the wrong places, compared to normal associations.

That being said, the true abomination is certainly has_and_belongs_to_many, which besides being pretty limited also doesn't seem to warn you of incorrect usage in any meaningful way at all. At least, that's how it seemed during my very first few hours using rails, almost two years ago (shortly thereafter has_many :through came out, and I haven't used habtm since).

The thing is, though, I'm not saying it isn't useful - I'm saying it's got too many error messages. Anything that confuses that many people in that many different ways is almost guaranteed to get cleaned up and clarified at some point in the future, and I'm just saying that I'm going to avoid it for as long as I can, because if I wait long enough, I can skip all the confusion. Sooner or later it'll end up elegant - the way lib/initializers cleaned up the ugliness of config/environment.rb - and I'd really rather just wait until then. I'll learn it if I have to, but the Ruby culture isn't about learning things because you have to, and if you look at learning as an investment, none of my best payoffs have come from things I learned because I had to. I always profit more from things I learn for fun. Everything you learn because you have to takes time away from things you learn because you want to, and for me personally the fun learning always pays off better than the obligated learning, so it's wise for me to minimize the obligated and maximize the fun.