Я использую миграции 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
вызове?
ПРИМЕЧАНИЕ . Я знаю, что использовать строковые столбцы в качестве первичных ключей не лучше, поэтому, пожалуйста, не отвечайте, просто говоря, что нужно добавить целочисленный первичный ключ. Я все равно могу добавить один, но этот вопрос все еще актуален.
источник
rake db:migrate
автоматически созданное определение схемы не содержит этого ограничения!Ответы:
К сожалению, я решил, что без использования
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
для создания первичного ключа, который не является целым числом (PostgreSQLserial
- это целое число, использующее последовательность):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
источник
self.primary_key=
вместоset_primary_key
, поскольку последний устарел.schema.rb
emp_id
снова будетinteger
столбец типа. Похоже, чтоschema.rb
никак не может хранитьexecute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"
.schema.rb
на параметрstructure.sql
viaconfig.active_record.schema_format
, который может быть либо:sql
или:ruby
. guides.rubyonrails.org/v3.2.13/…Это работает:
create_table :employees, :primary_key => :emp_id do |t| t.string :first_name t.string :last_name end change_column :employees, :emp_id, :string
Это может быть некрасиво, но конечный результат - именно то, что вам нужно.
источник
drop_table :employees
schema.rb
несинхронизированный объект ... он сохранит:primary_key => :emp_id
объявление, но приrake db:schema:load
его вызове, как это происходит в начале тестов, он создаст целочисленный столбец. Однако может случиться так, что если вы переключитесь на использованиеstructure.sql
(параметр конфигурации), тесты сохранят настройки и будут использовать этот файл для загрузки схемы.У меня есть один способ справиться с этим. Выполняемый 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
источник
Я пробовал это в 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 = {})
и прочтите строку:Я получил вышеуказанные идеи, как я показал. Вот метаданные таблицы после выполнения этой миграции:
[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=#
И из консоли Rails:
Loading development environment (Rails 4.2.1) => Unable to load pry >> Track.primary_key => "apple_id" >>
источник
schema.rb
генерируемый файл не отражаетstring
тип первичного ключа, поэтому при генерации базы данных с помощьюload
первичного ключа создается целое число.В Rails 5 вы можете
create_table :employees, id: :string do |t| t.string :first_name t.string :last_name end
См. Документацию create_table .
источник
before_create :set_id
в модель какой-то метод для присвоения значения первичного ключаПохоже, что можно сделать, используя такой подход:
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 первичным ключом для класса Widget, а затем вы должны заполнить поле при создании объектов. Вы должны иметь возможность сделать это с помощью обратного вызова before create.
Так что что-то вроде
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
источник
primary_key: true
вместо того, чтобы простоprimary
дать ответЯ использую Rails 2.3.5, и мой следующий способ работает с SQLite3
create_table :widgets, { :primary_key => :widget_id } do |t| t.string :widget_id # other column definitions end
В этом нет необходимости: id => false.
источник
you can't redefine the primary key column 'widgets'. To define a custom primary key, pass { id: false } to create_table.
После почти каждого решения, в котором говорится, что «это сработало для меня в базе данных X», я вижу комментарий исходного автора о том, что «у меня не сработало в Postgres». Настоящая проблема здесь на самом деле может заключаться в поддержке Postgres в Rails, которая не безупречна и, вероятно, была хуже в 2009 году, когда изначально был опубликован этот вопрос. Например, если я правильно помню, если вы используете Postgres, вы не можете получить полезный вывод из
rake db:schema:dump
.Я сам не ниндзя Postgres, я получил эту информацию из превосходного видео Ксавьера Шэя PeepCode на Postgres. Это видео фактически выходит из библиотеки Аарона Паттерсона, я думаю, Texticle, но я мог неправильно вспомнить. Но в остальном это довольно здорово.
В любом случае, если вы столкнулись с этой проблемой в Postgres, посмотрите, работают ли решения в других базах данных. Может быть, использовать
rails new
для создания нового приложения в виде песочницы или просто создать что-то вродеsandbox: adapter: sqlite3 database: db/sandbox.sqlite3 pool: 5 timeout: 5000
в
config/database.yml
.И если вы можете убедиться, что это проблема поддержки Postgres, и нашли исправление, внесите исправления в Rails или упакуйте исправления в гем, потому что база пользователей 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
А в модели employee.rb:
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
Так:
Кажется, 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
источник
Как насчет этого решения,
Модель внутреннего сотрудника, почему мы не можем добавить код, который будет проверять уникальность в столбце, например: Предположим, что сотрудник является моделью, поскольку у вас есть EmpId, который является строкой, тогда для этого мы можем добавить ": uniqueness => true" к EmpId
class Employee < ActiveRecord::Base validates :EmpId , :uniqueness => true end
Я не уверен, что это решение, но у меня это сработало.
источник
Я знаю, что это старая ветка, на которую я наткнулся ... но я немного шокирован, что никто не упомянул DataMapper.
Я считаю, что если вам нужно отклониться от соглашения ActiveRecord, я обнаружил, что это отличная альтернатива. Также это лучший подход для устаревших версий, и вы можете поддерживать базу данных «как есть».
Ruby Object Mapper (DataMapper 2) многообещающий и основан на принципах AREL!
источник
Добавление индекса работает для меня, я использую MySql, кстати.
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
источник