Redefining the primary key in Ruby on Rails
Rails are famous for their rule of “conventions prevail over configuration” (Convention over Configuration). However, sometimes, very rarely, some things have to be done differently. I want to share one of these cases in the article. I'll tell you how to make your primary key in a table (using Rails 4.2.0). Nothing complicated, in fact, but questions about how to do this are asked from time to time, and the answers are not always good.
Imagine you are writing a dictionary. In real life, you have a couple of dozen tables that are associated with the words table with all kinds of associations: one-to-many, many-to-many, many-to-many-through. For almost any query, in addition to the desired table, you need to output the word column as well, which means you need to join tables. A third-party worker is also processing a bunch of text on some radish, and the worker does not know that the words in the relational database also have identifiers. All this causes almost physical pain, and you think about it from time to time - maybe it is worth doing something different? By free will, you decide - the words in the words table are unique, so to hell with IDs, let the word itself be the primary key! In all ties belongs-to no longer needs to be joined, the worker does not need to verify identifiers, you can break down information into different databases without a headache.
For example, let us have 2 tables: words (with the primary field word) and definitions (with the belongs-to association to words). We write the migration:
In the models, we indicate the connections and the primary key for words:
This is enough, you do not need to execute execute in migrations and similar crutches. Check that we have received:
We changed the primary key in the model, but nothing broke. By the way, the last example may puzzle. Where does id come from if it is not in the schema or in the database? Here's what is hidden under the hood:
As you can see, the id field simply proxies the primary_key field.
Imagine you are writing a dictionary. In real life, you have a couple of dozen tables that are associated with the words table with all kinds of associations: one-to-many, many-to-many, many-to-many-through. For almost any query, in addition to the desired table, you need to output the word column as well, which means you need to join tables. A third-party worker is also processing a bunch of text on some radish, and the worker does not know that the words in the relational database also have identifiers. All this causes almost physical pain, and you think about it from time to time - maybe it is worth doing something different? By free will, you decide - the words in the words table are unique, so to hell with IDs, let the word itself be the primary key! In all ties belongs-to no longer needs to be joined, the worker does not need to verify identifiers, you can break down information into different databases without a headache.
For example, let us have 2 tables: words (with the primary field word) and definitions (with the belongs-to association to words). We write the migration:
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 the connections and the 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, you do not need to execute execute in migrations and similar crutches. Check that we have received:
w = Word.create(word: 'hello')
#
Word.find('hello')
Word Load (0.8ms) SELECT "words".* FROM "words" WHERE "words"."word" = $1 LIMIT 1 [["word", "hello"]]
=> #
d = Definition.create(word: w)
=> #
w.definitions
=> #]>
d.word
=> #
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 puzzle. Where does id come from if it is not in the schema or in the database? Here's what is hidden under the hood:
# activerecord/lib/active_record/attribute_methods/primary_key.rb:17
def id
sync_with_transaction_state
read_attribute(self.class.primary_key)
end
As you can see, the id field simply proxies the primary_key field.