Переопределяем первичный ключ в Ruby on Rails

от автора

Рельсы знамениты своим правилом «соглашения преобладают над конфигурацией» (Convention over Сonfiguration). Однако иногда, очень редко, некоторые вещи приходится делать по-другому. Одним из таких случаев я хочу поделиться в статье. Расскажу, как сделать свой первичный ключ в таблице (использую Rails 4.2.0). Ничего сложного, по сути, но вопросы о том, как это сделать, время от времени задают, а ответы не всегда хорошие.

Представим, что вы пишете словарь. В реальной жизни у вас пара десятков таблиц, которые связаны с таблицей words всевозможными ассоциациями: one-to-many, many-to-many, many-to-many-through. Для почти любого запроса нужно вывести в ответ кроме нужной таблицы еще и колонку word, а значит, надо джоинить таблицы. Еще у вас на каком-нибудь редисе сторонним воркером обрабатывается куча текста и воркер знать не знает, что у слов в реляционной бд есть еще и айдишники. Все это причиняет почти физическую боль и задумываетесь время от времени — может, стоит сделать что-то по-другому? Волевым усилием решаете — слова в таблице words уникальны, так к черту айдишники, пусть само слово будет первичным ключем! Во всех связях belongs-to больше не надо джоинить, воркеру не нужно сверять айдишники, можно разбить информацию по разным бд без головной боли.

Для примера, пусть у нас будут 2 таблицы: words (c первичным полем word) и definitions (с ассоциацией belongs-to к words). Пишем миграции:

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 

В моделях указываем связи и первичный ключ для words:

class Word < ActiveRecord::Base   self.primary_key = 'word'   has_many :definitions end 

class Definition < ActiveRecord::Base   belongs_to :word end 

Этого достаточно, не нужно делать execute в миграциях и подобные костыли. Проверим, что мы получили:

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 

Мы поменяли в модели первичный ключ, но ничего не сломалось. Кстати, последний пример может озадачить. Откуда берется id, если ни в схеме, ни в базе его нет? Вот что скрывается под капотом:

# activerecord/lib/active_record/attribute_methods/primary_key.rb:17 def id   sync_with_transaction_state   read_attribute(self.class.primary_key) end 

Как видим, поле id просто «проксирует» поле primary_key.

ссылка на оригинал статьи http://habrahabr.ru/post/253431/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *