Используя 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 80

14 ответов:

к сожалению, я определил, что это невозможно сделать без использования 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 создать первичный ключ, который не является целым числом ( является целым числом с использованием последовательности):

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

Так:

  1. use: id = > false, чтобы не генерировать целочисленный первичный ключ
  2. используйте нужный тип данных и добавьте: null = > false
  3. добавьте уникальный индекс в этот столбец

Кажется, что 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, слишком!

добавление индекса работает для меня, я использую 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