ruby-on-rails - type - rake db create




使用Rails,我如何設置我的主鍵不是一個整型列? (10)

這工作:

create_table :employees, :primary_key => :emp_id do |t|
  t.string :first_name
  t.string :last_name
end
change_column :employees, :emp_id, :string

這可能不太好,但最終的結果正是你想要的。

我正在使用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),而不必在execute調用中編寫SQL?

:我知道最好不要使用字符串列作為主鍵,所以請不要只是說添加一個整數主鍵的答案。 無論如何,我可以添加一個,但這個問題仍然有效。


不幸的是,我已經確定不使用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);"

正如Sean McCleary所說 ,你的ActiveRecord模型應該使用set_primary_key設置主鍵:

class Employee < ActiveRecord::Base
  set_primary_key :emp_id
  ...
end

在Rails 5中你可以做到

create_table :employees, id: :string do |t|
  t.string :first_name
  t.string :last_name
end

請參閱create_table文檔


幾乎所有的解決方案都說“這對我在X數據庫中工作”,我看到原始海報的評論“對Postgres沒有任何作用”。 這裡真正的問題實際上可能是Postgres對Rails的支持,這不完美,在2009年這個問題最初發佈時可能更糟糕。 例如,如果我沒有記錯的話,如果你在Postgres上,你基本上無法從rake db:schema:dump獲得有用的輸出rake db:schema:dump

我本人不是Postgres忍者,我從Xavier Shay在Postgres上的優秀PeepCode視頻中獲得了這些信息。 那個視頻實際上忽略了Aaron Patterson的一個圖書館,我認為Texticle,但我可能記得錯誤。 但除此之外,這非常棒。

無論如何,如果你在Postgres上遇到這個問題,看看解決方案是否在其他數據庫中工作。 也許使用rails new來生成一個新的應用程序作為沙箱,或者只是創建類似的東西

sandbox:
  adapter: sqlite3
  database: db/sandbox.sqlite3
  pool: 5
  timeout: 5000

config/database.yml

如果您可以驗證它是Postgres支持問題,並且您找出修復方法,請向Rails提供補丁或將修補程序打包為gem,因為Rails社區中的Postgres用戶群非常大,主要得益於Heroku 。


我在Rails 3和MySQL上使用的技巧是這樣的:

create_table :events, {:id => false} do |t|
  t.string :id, :null => false
end

add_index :events, :id, :unique => true

所以:

  1. 使用:id => false以便不生成整數主鍵
  2. 使用所需的數據類型,並添加:null => false
  3. 在該列上添加一個唯一的索引

似乎MySQL將非空列上的唯一索引轉換為主鍵!


我已經在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,:布爾值。

正如我所展示的那樣,我得到了上述的ide。 這是運行此遷移後的表元數據:

[[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"
>>

我發現了一個適用於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

我知道這是我偶然發現的一個舊線程......但我有點震驚沒有人提到DataMapper。

我發現如果你需要偏離ActiveRecord約定,我發現它是一個很好的選擇。 此外,它是遺留下來的更好方法,您可以“按原樣”支持數據庫。

Ruby Object Mapper (DataMapper 2)也擁有很多承諾並基於AREL原則構建!


看起來有可能使用這種方法:

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_id成為Widget類的主鍵,然後在創建對象時填充該字段。 你應該可以使用before create callback來做到這一點。

所以,沿著這條路線

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

這個解決方案如何,

在Employee模型裡面,為什麼我們不能添加將檢查coloumn唯一性的代碼,例如:假設Employee是Model,因為你有EmpId這是字符串,那麼我們可以為EmpId添加“:uniqueness => true”

    class Employee < ActiveRecord::Base
      validates :EmpId , :uniqueness => true
    end

我不確定這是否是解決方案,但這對我有效。





primary-key