Используя Rails, как я могу установить свой первичный ключ, чтобы он не был столбцом с целым типом?
Я использую Rails-миграции для управления схемой базы данных, и я создаю простую таблицу, где я бы хотел использовать нецелое значение в качестве первичного ключа (в частности, строки). Чтобы отвлечься от моей проблемы, скажем, есть таблица employees
, где сотрудники идентифицируются буквенно-цифровой строкой, например. "134SNW"
.
Я попытался создать таблицу в миграции следующим образом:
create_table :employees, {:primary_key => :emp_id} do |t|
t.string :emp_id
t.string :first_name
t.string :last_name
end
То, что это дает мне, похоже на то, что он полностью проигнорировал строку t.string :emp_id
и пошел вперед и сделал ее целым столбцом. Есть ли другой способ, чтобы рельсы генерировали ограничение PRIMARY_KEY (я использую PostgreSQL) для меня, без необходимости писать SQL в вызове execute
?
ПРИМЕЧАНИЕ. Я знаю, что не лучше использовать строковые столбцы в качестве первичных ключей, поэтому, пожалуйста, не отвечайте, просто добавляя целочисленный первичный ключ. Я могу добавить его в любом случае, но этот вопрос все еще действителен.
Ответы
Ответ 1
К сожалению, я решил, что это невозможно сделать без использования execute
.
Почему это не работает
Изучив источник ActiveRecord, мы можем найти код для create_table
:
В schema_statements.rb
:
def create_table(table_name, options={})
...
table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
...
end
Итак, мы видим, что при попытке указать первичный ключ в параметрах create_table
он создает первичный ключ с указанным именем (или, если ни один не указан, id
). Он делает это, вызывая тот же метод, который вы можете использовать внутри блока определения таблицы: primary_key
.
В schema_statements.rb
:
def primary_key(name)
column(name, :primary_key)
end
Это просто создает столбец с указанным именем типа :primary_key
. В стандартных адаптерах базы данных установлено следующее:
PostgreSQL: "serial primary key"
MySQL: "int(11) DEFAULT NULL auto_increment PRIMARY KEY"
SQLite: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"
Обходной путь
Поскольку мы зацикливаемся на них в качестве типов первичных ключей, мы должны использовать execute
для создания первичного ключа, который не является целым числом (PostgreSQL serial
является целым числом с использованием последовательности):
create_table :employees, {:id => false} do |t|
t.string :emp_id
t.string :first_name
t.string :last_name
end
execute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"
И как Шон Макклири отметил, ваша модель ActiveRecord должна установить первичный ключ, используя set_primary_key
:
class Employee < ActiveRecord::Base
set_primary_key :emp_id
...
end
Ответ 2
У меня есть один способ справиться с этим. Выполненный SQL - это ANSI SQL, поэтому он, скорее всего, будет работать с большинством реляционных баз данных, совместимых с ANSI SQL. Я тестировал, что это работает для MySQL.
Миграция:
create_table :users, :id => false do |t|
t.string :oid, :limit => 10, :null => false
...
end
execute "ALTER TABLE users ADD PRIMARY KEY (oid);"
В вашей модели сделайте следующее:
class User < ActiveRecord::Base
set_primary_key :oid
...
end
Ответ 3
Это работает:
create_table :employees, :primary_key => :emp_id do |t|
t.string :first_name
t.string :last_name
end
change_column :employees, :emp_id, :string
Это может быть не очень красиво, но конечный результат - именно то, что вы хотите.
Ответ 4
Похоже, что можно использовать этот подход:
create_table :widgets, :id => false do |t|
t.string :widget_id, :limit => 20, :primary => true
# other column definitions
end
class Widget < ActiveRecord::Base
set_primary_key "widget_id"
end
Это приведет к тому, что column widget_id станет основным ключом для класса Widget, тогда вам нужно заполнить поле при создании объектов. Вы должны быть в состоянии сделать это, используя запрос перед созданием.
Итак, что-то вроде строк
class Widget < ActiveRecord::Base
set_primary_key "widget_id"
before_create :init_widget_id
private
def init_widget_id
self.widget_id = generate_widget_id
# generate_widget_id represents whatever logic you are using to generate a unique id
end
end
Ответ 5
Я на Rails 2.3.5, и мой следующий способ работает с SQLite3
create_table :widgets, { :primary_key => :widget_id } do |t|
t.string :widget_id
# other column definitions
end
Нет необходимости: id = > false.
Ответ 6
Я попробовал это в Rails 4.2. Чтобы добавить свой настраиваемый первичный ключ, вы можете написать свою миграцию как:
# tracks_ migration
class CreateTracks < ActiveRecord::Migration
def change
create_table :tracks, :id => false do |t|
t.primary_key :apple_id, :string, limit: 8
t.string :artist
t.string :label
t.string :isrc
t.string :vendor_id
t.string :vendor_offer_code
t.timestamps null: false
end
add_index :tracks, :label
end
end
При просмотре документации column(name, type, options = {})
и прочитайте строку:
Параметр type
обычно является одним из типов родств миграции, который является одним из следующих:: primary_key,: string,: text,: integer,: float,: decimal,: datetime,: time,: date,: binary,: boolean.
Я получил приведенные выше, как я показал. Ниже приведены метаданные таблицы после выполнения этой миграции:
[[email protected]_track (master)]$ rails db
psql (9.2.7)
Type "help" for help.
music_track_development=# \d tracks
Table "public.tracks"
Column | Type | Modifiers
-------------------+-----------------------------+-----------
apple_id | character varying(8) | not null
artist | character varying |
label | character varying |
isrc | character varying |
vendor_id | character varying |
vendor_offer_code | character varying |
created_at | timestamp without time zone | not null
updated_at | timestamp without time zone | not null
title | character varying |
Indexes:
"tracks_pkey" PRIMARY KEY, btree (apple_id)
"index_tracks_on_label" btree (label)
music_track_development=#
И из консоли Rails:
Loading development environment (Rails 4.2.1)
=> Unable to load pry
>> Track.primary_key
=> "apple_id"
>>
Ответ 7
После почти каждого решения, в котором говорится, что "это сработало для меня в базе X", я вижу комментарий по оригинальному плакату о том, что "не работал у меня в Postgres". Настоящей проблемой здесь может быть поддержка Postgres в Rails, что не является безупречным и, вероятно, было хуже в 2009 году, когда этот вопрос был первоначально опубликован. Например, если я правильно помню, если вы на Postgres, вы в принципе не можете получить полезный вывод из rake db:schema:dump
.
Я не сам ниндзя Postgres, я получил эту информацию от Xavier Shay отличного видео PeepCode на Postgres. Это видео на самом деле пропускает библиотеку Аарона Паттерсона, я думаю, что Texticle, но я мог вспомнить неправильно. Но кроме этого это довольно хорошо.
В любом случае, если вы столкнулись с этой проблемой в Postgres, посмотрите, работают ли решения в других базах данных. Возможно, используйте rails new
для создания нового приложения в виде песочницы или просто создайте что-то вроде
sandbox:
adapter: sqlite3
database: db/sandbox.sqlite3
pool: 5
timeout: 5000
в config/database.yml
.
И если вы можете проверить, что это проблема с поддержкой Postgres, и вы выяснили исправление, внесите исправления в Rails или упакуйте исправления в самоцвет, потому что пользовательская база Postgres в сообществе Rails довольно большая, в основном благодаря Героку.
Ответ 8
Я нашел решение для этого, которое работает с Rails 3:
Файл миграции:
create_table :employees, {:primary_key => :emp_id} do |t|
t.string :emp_id
t.string :first_name
t.string :last_name
end
И в модели employee.rb:
self.primary_key = :emp_id
Ответ 9
В Rails 5 вы можете сделать
create_table :employees, id: :string do |t|
t.string :first_name
t.string :last_name
end
См. документация create_table.
Ответ 10
вам нужно использовать опцию: id = > false
create_table :employees, :id => false, :primary_key => :emp_id do |t|
t.string :emp_id
t.string :first_name
t.string :last_name
end
Ответ 11
Трюк, который работал у меня на Rails 3 и MySQL, был следующим:
create_table :events, {:id => false} do |t|
t.string :id, :null => false
end
add_index :events, :id, :unique => true
Итак:
- use: id = > false, чтобы не генерировать целочисленный первичный ключ
- используйте нужный тип данных и добавьте: null = > false
- добавить уникальный индекс в этот столбец
Кажется, что MySQL преобразует уникальный индекс в столбце non null в первичный ключ!
Ответ 12
Как насчет этого решения,
Внутри модели Employee почему мы не можем добавить код, который будет проверять уникальность в coloumn, например: Предположим, что Employee является Model, поскольку у вас есть EmpId, который является строкой, для чего мы можем добавить ": uniqueness = > true" для EmpId
class Employee < ActiveRecord::Base
validates :EmpId , :uniqueness => true
end
Я не уверен, что это решение, но это сработало для меня.
Ответ 13
Я знаю, что это старый поток, на который я наткнулся... но я в шоке, никто не упоминал DataMapper.
Я считаю, что если вам нужно отклониться от соглашения ActiveRecord, я обнаружил, что это отличная альтернатива. Кроме того, это лучший подход к наследию, и вы можете поддерживать базу данных "как есть".
Ruby Object Mapper (DataMapper 2) содержит много обещаний и основывается на принципах AREL тоже!
Ответ 14
Добавление индекса работает для меня, я использую MySql btw.
create_table :cards, {:id => false} do |t|
t.string :id, :limit => 36
t.string :name
t.string :details
t.datetime :created_date
t.datetime :modified_date
end
add_index :cards, :id, :unique => true