The rails are famous for their rule “agreements prevail over configuration” (Convention over onfiguration). However, sometimes, very rarely, some things have to be done differently. One of these cases I want to share in the article. I'll tell you how to make your primary key in a table (I use Rails 4.2.0). Nothing complicated, in fact, but questions about how to do this, from time to time, are asked, and the answers are not always good.
Imagine that you are writing a dictionary. In real life, you have a couple of dozen tables that are associated with the words table with various associations: one-to-many, many-to-many, many-to-many-through. For almost any query, you need to return in addition to the desired table also the word column, which means you need to join the tables. You also have a bunch of text processed by a third-party worker on a radish and the worker doesn’t know that the words in the relational database also have IDs. All this causes almost physical pain and thinks from time to time - maybe it is worth doing something different? With a determined effort, you decide - the words in the words table are unique, so to hell are the IDs, let the word itself be the primary key! In all the belongs-to links, there is no need to join anymore, the worker doesn’t need to check IDs, you can split the information on different databases without a headache.
For example, let us have 2 tables: words (with the primary field word) and definitions (with the association belongs-to words). We write migrations:
class CreateWords < ActiveRecord::Migration def change create_table :words, id: false do |t| t.string :word, null: false t.timestamps null: false end add_index :words, :word, unique: true end end
class CreateDefinitions < ActiveRecord::Migration def change create_table :definitions do |t| t.string :word_id, null: false t.timestamps null: false end add_index :definitions, :word_id end end
In the models, we indicate connections and primary key for words:
')
class Word < ActiveRecord::Base self.primary_key = 'word' has_many :definitions end
class Definition < ActiveRecord::Base belongs_to :word end
This is enough, no need to do execute in migrations and similar crutches. Check what we got:
w = Word.create(word: 'hello')
Word.find('hello') Word Load (0.8ms) SELECT "words".* FROM "words" WHERE "words"."word" = $1 LIMIT 1 [["word", "hello"]] => #<Word word: "hello", created_at: "2015-03-16 21:35:59", updated_at: "2015-03-16 21:35:59">
d = Definition.create(word: w) => #<Definition id: 2, word_id: "hello", created_at: "2015-03-16 21:36:22", updated_at: "2015-03-16 21:36:22">
w.definitions => #<ActiveRecord::Associations::CollectionProxy [#<Definition id: 2, word_id: "hello", created_at: "2015-03-16 21:36:22", updated_at: "2015-03-16 21:36:22">]>
d.word => #<Word word: "hello", created_at: "2015-03-16 21:35:59", updated_at: "2015-03-16 21:35:59">
d.word_id => "hello"
w.id => "hello
We changed the primary key in the model, but nothing broke. By the way, the last example may be puzzling. Where does the id come from, if it is not in the schema or in the database? Here is what is under the hood:
As you can see, the id field simply “proxies” the primary_key field.