I found an interesting bug in Rails 2.2.2 yesterday. I couldn’t find a similar bug on the rails lighthouse so created a new ticket. What was most interesting though, was how quick the rails core team picked up the bug and assigned it to someone.
It turns out that the bug had already been fixed in the current master branch of the rails git repo, though apparently no one had noticed it’s existence because I can’t find any references to this anywhere. I guess the fix in activerecord, which is almost identical to my fix below, will form part of the next release whenever that is.
I assume this is probably also the case for other has_* relationships, but have not verified.
I have a has_many association from class Foo to class Bar, where, for this specific relationship, the primary key on Foo is not id, nor is the foreign key on Bar id.
class Foo
has_many, :bars, :primary_key => 'a_non_standard_key_name', :foreign_key => 'another_non_standard_key_name'
end
The relationship is one way, I have no need to navigate from Bar back to Foo, but only call a_foo.bars.
This works fine when working with a single object, but breaks down when you want to do eager association preloading to avoid n+1 query problem of loading bars for many foos.
When performing the following you find that
f = Foo.find :all, :include => :Bar
f.bars = [SOMETHING_UNEXPECTED]
The reason is that ActiveRecord creates the preloading query based on the default primary key of Foo (normally id).
It queries for Bar.another_non_standard_key_name matching Foo.id not Foo.a_non_standard_key_name
This causes seriously unexpected behaviour, and could easily go unnoticed since no errors are thrown.
I have found the hook in ActiveRecord where this functionality should be included and monkey patched for my system, because I need it now. I can’t vouch for it’s correctness, but we have many many specs for our product and none of them have broken because of this.
I’m running frozen rails 2.2.2
vendor/activerecord/lib/active_record/association_preload.rb, line 221
Change
primary_key_name = reflection.through_reflection_primary_key_name
to
primary_key_name = reflection.through_reflection_primary_key_name || reflection.options[:primary_key]
Hope this helps someone!