Используя 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
и пошел вперед и сделал это целочисленном столбце. Есть ли какой-то другой способ заставить rails генерировать ограничение PRIMARY_KEY (я использую PostgreSQL) для меня, без необходимости писать SQL в execute
звонок?
Примечание: Я знаю, что не лучше использовать строковые столбцы в качестве первичных ключей, поэтому, пожалуйста, не отвечайте, просто говоря, чтобы добавить целочисленный первичный ключ. Я могу добавить один, но этот вопрос остается в силе.
14 ответов:
к сожалению, я определил, что это невозможно сделать без использования
execute
.почему это не работает
изучив источник ActiveRecord, мы можем найти код для
create_table
: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
.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
создать первичный ключ, который не является целым числом ( является целым числом с использованием последовательности):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
это работает:
create_table :employees, :primary_key => :emp_id do |t| t.string :first_name t.string :last_name end change_column :employees, :emp_id, :string
это может быть не очень красиво, но конечный результат именно то, что вы хотите.
у меня есть один способ справиться с этим. Выполняемый 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
похоже, что это можно сделать с помощью этого подхода:
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
что сделает столбец widget_id первичным ключом для класса виджета, то это до вас, чтобы заполнить поле при создании объектов. Вы должны быть в состоянии сделать это с помощью до создания обратного вызова.
Так что-то вроде
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
Я нахожусь на рельсах 2.3.5 и мой следующий способ работает с SQLite3
create_table :widgets, { :primary_key => :widget_id } do |t| t.string :widget_id # other column definitions end
нет необходимости :id = > false.
Я пробовал это в 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 = {})
и прочел строчку:The
type
параметр обычно является одним из миграций собственного типа, которая является одним из следующих: :primary_key, :строка, текст, целое число, :поплавок, :десятичные, :дату и время :время :дата :двоичный :булево.я получил выше Иды как я уже показал. Вот таблица метаданных после запуска этой миграции:
[arup@music_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=#
и от рельсов консоли:
Loading development environment (Rails 4.2.1) => Unable to load pry >> Track.primary_key => "apple_id" >>
в Rails 5 Вы можете сделать
create_table :employees, id: :string do |t| t.string :first_name t.string :last_name end
посмотреть create_table documentation.
после почти каждого решения, которое говорит:" это сработало для меня на базе данных X", я вижу комментарий оригинального плаката к эффекту " не сработало для меня на Postgres."Реальной проблемой здесь может быть поддержка Postgres в Rails, которая не безупречна и, вероятно, была хуже в 2009 году, когда этот вопрос был первоначально опубликован. Например, если я правильно помню, если вы находитесь на Postgres, вы в основном не можете получить полезный результат от
rake db:schema:dump
.Я не Постгрес ниндзя сам я получил эту информацию из превосходного видео PeepCode Ксавье шея на Postgres. Это видео на самом деле выходит из библиотеки Аарона Паттерсона, я думаю, что Texticle, но я могу ошибаться. Но в остальном это довольно здорово.
в любом случае, если вы столкнулись с этой проблемой на Postgres, посмотрите, работают ли решения в других базах данных. Может быть, использовать
rails new
создать новое приложение в песочнице, или просто создать что-то вродеsandbox: adapter: sqlite3 database: db/sandbox.sqlite3 pool: 5 timeout: 5000
на
config/database.yml
.и если вы можете проверить, что это проблема поддержки Postgres, и вы выясните исправление, пожалуйста, Внесите исправления в Rails или упакуйте свои исправления в gem, потому что база пользователей Postgres в сообществе Rails довольно велика, в основном благодаря Heroku.
Я нашел решение этой проблемы, которое работает с Rails 3:
миграция файл:
create_table :employees, {:primary_key => :emp_id} do |t| t.string :emp_id t.string :first_name t.string :last_name end
и в сотрудником.модель РБ:
self.primary_key = :emp_id
трюк, который работал для меня на 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 преобразует уникальный индекс на ненулевом столбце в первичный ключ!
вы должны использовать опцию: 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
Как насчет такого решения,
внутри модели сотрудника почему мы не можем добавить код, который будет проверять уникальность в coloumn, например: предположим, что сотрудник является моделью в том, что у вас есть EmpId, который является строкой, тогда для этого мы можем добавить ":уникальность => правда" к EmpId
class Employee < ActiveRecord::Base validates :EmpId , :uniqueness => true end
Я не уверен, что это решение, но это работает для меня.
Я знаю, что это старая нить, на которую я наткнулся... но я немного шокирован, что никто не упомянул DataMapper.
Я считаю, что если вам нужно отклониться от конвенции ActiveRecord, я обнаружил, что это отличная альтернатива. Также его лучший подход для наследия, и вы можете поддерживать базу данных "как есть".
Ruby Object Mapper (DataMapper 2) держит много посыл и строит на принципах AREL, слишком!