Rails has had the has_one :through association for a while now, and you probably use it on occasion. But, has it ever given you the heebie-jeebies a little bit? Maybe something happens now and then that doesn’t seem quite right? Well, the reason for this is that HasOneThroughAssociation, the class that ActiveRecord uses (shockingly) to implement has_one :through associations, is a subclass of HasManyThroughAssociation.
Yes, oddly enough, a has_one :through association is a collection that is special-cased to always return just one element. Seems kind of dirty, doesn’t it?
I have two, related, problems with this inheritance relationship. First, it violates Liskov substitutability. One cannot in any way argue that a has_one :through association IS A has_many :through association; substituting one for the other would not be likely to maintain a program’s correctness. Second, the inheritance relationship exists to share implementation, not interface. Among others, the Gang of Four have described, persuasively, why this is a terrible idea.
More practically, I’ve now run into two serious bugs caused by this relationship. The first one caused newly created has_one :through associations to return an empty array rather than nil. I fixed that here, with the help of wunderkind David Stevenson. The second I ran into in the last few days while working on this patch, which I’ve written about here. It turns out that trying to get #method_missing to behave sensibly for all non-collection associations is difficult when one of those associations inherits a pile of collection-specific functionality.
So, in order to make my #method_missing patch work I had to rewrite HasOneThroughAssociation with a more appropriate superclass (I used HasOneAssociation). You can find that patch here. Hopefully this change will make its way into the Rails codebase and will make lives easier for generations to come.