Note: Spots is doing technobabble again! Feel free to skip ahead if you don’t enjoy reading about Third Normal Form, or know what that is.
I picked Spotcheck back up, and remembered the stumbling point I was at. I have created a Countries table with the columns iso_code and name. Simple, right? You’d have things like {"uk", "United Kingdom"}.
The iso_code column is also the primary key column. Great, but when I tried to create a foreign key constraint in Rails using t.references, I was getting errors because country.id didn’t exist. It blindly assumes that id is the column at the other end. Surprisingly, it doesn’t even consult the primary_key method on the model – which I would have expected!
How to solve this, then? I decided to look at the documentation, which is usually revealing in these cases, but there was no reference on how to do it in Rails’ official documentation. I dove into the source code and after peeling back a few layers, I found that the foreign_key options hash accepts a primary_key key. This was the ticket! The magic incantation I needed was:
t.references :country, null: false, foreign_key: { primary_key: :iso_code }, type: :string
And now everything works correctly. I decided to write this up for the next troubled soul on the Web that made a custom database schema and doesn’t want to waste time wading through the source of ActiveRecord…
I do wish the documentation covered this better. I know that you’re supposed to just do the default database schema, and usually I will do just that. But they make a lot of references to understanding people inherit legacy database schemas that they need to deal with, and that defaults are there for the common case but can always be overridden. And this can be overridden! If you aren’t afraid of digging into the source code.