Just a quick couple of reasons why you should not use HABTM:
- It’s too magical. The join table is hidden, and with the emphasis on
has_many :throughthe documentation is less available for HABTM. - The vast majority of the time, your join model already or will soon have additional data that is much more difficult to track in a HABTM relationship.
- There’s no real harm in starting off with HMT over HABTM - the former gives everything the latter does and more.
- It can be a major pain to transition a HABTM relationship to HMT after the fact; this is especially true if your join table is already big.
HABTM gives you the _ids method for free, which makes handling a set of checkboxes easier. HMT doesn’t give you that.
Actually, you do get the x_id getters with HMT. You’re correct that you don’t get the corresponding setters, though, so checkbox handling is a tiny bit more complicated with HMT than with HABTM. I’ll give it a +1 goodness point to recognize that - but the balance shows it’s still EVIL.
Obviously, if your “join table” has additional attributes, then it’s not a join table at all - it’s an entity. In that case, you absolutely must use HMT. When all you’re doing is describing a many-to-many relationship (which is what I’ve seen in the vast majority of cases in 25 years of database programming), HABTM is the simplest thing that could possibly work.
How could it be a “major pain” to convert, when you actually need additional attributes? What difference does it make how large the table is?
Fundamentally, why create a model (and its tests) when you don’t need to? Rails is agile - we should be, too.
///ark
Sure, HABTM is sufficient for truly many-to-many relationships without a join model. In my (admittedly shorter, at 10 years) experience, though, it seems that I end up moving to a real join model in 90% of cases - even if it’s only to track when the association was created.
The difficulties I’ve seen in converting revolve around the table’s keys - removing the composite primary key and adding a new id PK. This takes significantly longer on large tables, and is generally not a fun thing to do on a production DB. If you’re already set up to use a real join model, this obviously isn’t an issue, since you just go ahead and add the new columns.
As for creating a new model and tests, I don’t see this as a problem. If you are simply replacing HABTM with HMT, then the tests for the HMT model will be trivial (asserting solely that you’ve got the appropriate associations declared) - and if there’s anything more complex going on, you’ll be in much better shape later on.
That’s a good point - if you need to add a housekeeping column like created_at, then it’s gotta be a model. Whether that’s a truly useful attribute or not is going to be defined by the users (directly or indirectly). I have to say that it hasn’t often been required in my work. Also, I would say that that’s the kind of thing that you would add up front, and hence you would start with HMT.
I have never used single primary keys for join tables in the past. The “natural” combination of the joinees’ keys is sufficient. Rails has other ideas, of course. Which is another reason not to use HMT unless you need it - more storage (potentially a third more).
I really try to avoid decisions based on what -might- happen later on. I know this isn’t as easy a rule to follow when you’re working with databases, but it is still a major part of my thinking.
The reason I mentioned the additional tests and models is that they contribute to the sheer number of files in the project that I (or a newbie) has to wade through. Other things being equal, less is better.
///ark