📜 ⬆️ ⬇️

Overriding Primary Key in Ruby on Rails

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 word: "hello", created_at: "2015-03-16 21:35:59", updated_at: "2015-03-16 21:35:59"> 

 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:

 # 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.

Source: https://habr.com/ru/post/253431/


All Articles