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:

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.

Also popular now: