Has_many, through, and custom primary keys

When attempting to use a “has_many :through” association with three
models using non-standard primary keys, Rails is not constructing SQL
finder query properly. For some reason it is not using correct primary
key column for the “middle” table. Here is a detailed example:

create_table :books, :primary_key => :my_book_id, :force => true do |t|
t.column :name, :string
end

create_table :chapters, :primary_key => :my_chapter_id, :force => true
do |t|
t.column :parent_book_id, :integer
t.column :name, :string
end

create_table :paragraphs, :primary_key => :paragraph_id, :force => true
do |t|
t.column :parent_chapter_id, :integer
t.column :name, :string
end

class Book < ActiveRecord::Base
set_primary_key(:my_book_id)
has_many :chapters, :foreign_key => :parent_book_id
has_many :paragraphs, :through => :chapters, :source => :paragraphs
end

class Chapter < ActiveRecord::Base
set_primary_key(:my_chapter_id)
has_many :paragraphs, :foreign_key => :parent_chapter_id
belongs_to :book, :foreign_key => :parent_book_id
end

class Paragraph < ActiveRecord::Base
set_primary_key(:my_paragraph_id)
belongs_to :chapter, :foreign_key => :parent_chapter_id
end

Now, when I run the following from the console:

Book.find(1).paragraphs

The following SQL gets sent to MySQL:

SELECT paragraphs.* FROM paragraphs INNER JOIN chapters ON
paragraphs.parent_chapter_id = chapters.my_paragraph_id WHERE
chapters.parent_book_id = 1

The ON clause is in error. Instead of saying:

ON paragraphs.parent_chapter_id = chapters.my_paragraph_id

it should be:

ON paragraphs.parent_chapter_id = chapters.my_chapter_id

Am I missing some option in one of my associations? Or am I hitting a
Rails bug?

BTW, I am running Rails 1.2.1

Thanks in advance,
Fyodor Golos

I checked out http://dev.rubyonrails.org/svn/rails/tags/rel_1-2-1 and
started digging into this problem…

def construct_joins(custom_joins = nil)
polymorphic_join = nil
if @reflection.through_reflection.options[:as] ||
@reflection.source_reflection.macro == :belongs_to
reflection_primary_key = @reflection.klass.primary_key
source_primary_key =
@reflection.source_reflection.primary_key_name
else
reflection_primary_key =
@reflection.source_reflection.primary_key_name
source_primary_key = @reflection.klass.primary_key
if @reflection.source_reflection.options[:as]
polymorphic_join = “AND %s.%s = %s” % [
@reflection.table_name,
“#{@reflection.source_reflection.options[:as]}_type”,
@owner.class.quote_value(@reflection.through_reflection.klass.name)
]
end
end
“INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]}
#{custom_joins}” % [
@reflection.through_reflection.table_name,
@reflection.table_name, reflection_primary_key,
@reflection.through_reflection.table_name, source_primary_key,
polymorphic_join
]
end

Skipping down to line 181:

source_primary_key = @reflection.klass.primary_key

Unless I am totally mistaken, it should have read like this:

source_primary_key = @reflection.through_reflection.klass.primary_key

Making that change does not break any Rails tests (which probably
doesn’t mean much, since Rails tests are probably not using non-standard
primary key column names) and fixes my problem by constructing correct
SQL statement.

It is quite possible that line 177 also needs to be modified in a
similar manner to read:

reflection_primary_key =
@reflection.through_reflection.klass.primary_key

Can someone more knowledgeable confirm this before I make and submit a
patch?

Thank you,
Fyodor Golos