使用Rails,如何将主键设置为不是整数类型的列?

Rud*_*ski 85 database migration ruby-on-rails primary-key

我正在使用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
Run Code Online (Sandbox Code Playgroud)

这给了我什么似乎完全忽略了这条线t.string :emp_id并继续前进并使它成为一个整数列.有没有其他方法让rails为我生成PRIMARY_KEY约束(我正在使用PostgreSQL),而不必在execute调用中编写SQL ?

注意:我知道最好不要使用字符串列作为主键,所以请不要回答只是说添加一个整数主键.无论如何我可以添加一个,但这个问题仍然有效.

Rud*_*ski 107

不幸的是,我已经确定不使用它就不可能做到这一点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
Run Code Online (Sandbox Code Playgroud)

因此,我们可以看到,当您尝试在create_table选项中指定主键时,它会创建具有指定名称的主键(或者,如果未指定,则创建id).它通过调用您可以在表定义块中使用的相同方法来完成此操作:primary_key.

schema_statements.rb:

def primary_key(name)
  column(name, :primary_key)
end
Run Code Online (Sandbox Code Playgroud)

这只是创建一个具有指定名称类型的列:primary_key.这在标准数据库适配器中设置如下:

PostgreSQL: "serial primary key"
MySQL: "int(11) DEFAULT NULL auto_increment PRIMARY KEY"
SQLite: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL"
Run Code Online (Sandbox Code Playgroud)

解决方法

由于我们坚持使用这些作为主键类型,我们必须使用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);"
Run Code Online (Sandbox Code Playgroud)

正如Sean McCleary所提到的,您的ActiveRecord模型应该使用set_primary_key以下方法设置主键:

class Employee < ActiveRecord::Base
  set_primary_key :emp_id
  ...
end
Run Code Online (Sandbox Code Playgroud)

  • 实际上,现在你应该使用`self.primary_key =`而不是`set_primary_key`,因为后者已被弃用. (18认同)
  • 解决方法将使rake db:test:clone或rake db:schema:load以创建emp_id作为整数列. (15认同)
  • 这种方法的问题是,当您从`schema.rb`设置数据库时,`emp_id`将再次成为`integer`类型列.似乎`schema.rb`不能存储`execute'ALTER TABLE员工ADD PRIMARY KEY(emp_id);"`以任何方式. (8认同)
  • @DonnyKurnia @Tintin81您可以通过`config.active_record.schema_format`设置将架构转储从`schema.rb`切换到`structure.sql`,这可能是`:sql`或`:ruby`.http://guides.rubyonrails.org/v3.2.13/migrations.html#what-are-schema-files-for (4认同)
  • 这是*唯一*解决方案对我有用.我希望它可以帮助其他人:http://stackoverflow.com/a/15297616/679628 (2认同)

Aus*_*tin 21

这有效:

create_table :employees, :primary_key => :emp_id do |t|
  t.string :first_name
  t.string :last_name
end
change_column :employees, :emp_id, :string
Run Code Online (Sandbox Code Playgroud)

它可能不是很漂亮,但最终的结果正是你想要的.

  • 不幸的是,这个方法也给我们留下了一个不同步的`schema.rb` ...它将保留`:primary_key =>:emp_id`声明,但是当调用`rake db:schema:load`时,它是在测试开始时,它将产生一个整数列.但是,如果切换到使用`structure.sql`(config选项),测试可能会保留设置并使用该文件加载架构. (5认同)
  • 对于向下迁移:`drop_table:employees` (2认同)

Sea*_*ary 18

我有一种处理方式.执行的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);"
Run Code Online (Sandbox Code Playgroud)

在你的模型中这样做:

class User < ActiveRecord::Base
    set_primary_key :oid
    ...
end
Run Code Online (Sandbox Code Playgroud)


Aru*_*hit 9

我在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
Run Code Online (Sandbox Code Playgroud)

在查看文档column(name, type, options = {})并阅读该行时:

type参数通常是迁移本机类型之一,它是以下之一:: primary_key,:string,:text,:integer,:float,:decimal,:datetime,:time,:date,:binary,:boolean .

正如我所示,我得到了上述想法.以下是运行此迁移后的表元数据:

[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=#
Run Code Online (Sandbox Code Playgroud)

从Rails控制台:

Loading development environment (Rails 4.2.1)
=> Unable to load pry
>> Track.primary_key
=> "apple_id"
>>
Run Code Online (Sandbox Code Playgroud)

  • 但问题是,生成的`schema.rb`文件不反映主键的`string`类型,因此在使用`load`生成数据库时,主键被创建为整数. (3认同)

Pav*_*uva 9

在Rails 5中你可以做到

create_table :employees, id: :string do |t|
  t.string :first_name
  t.string :last_name
end
Run Code Online (Sandbox Code Playgroud)

请参见create_table文档.


pau*_*erd 8

看起来可以使用这种方法:

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
Run Code Online (Sandbox Code Playgroud)

这将使列widget_id成为Widget类的主键,然后由您在创建对象时填充该字段.您应该可以使用之前的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
Run Code Online (Sandbox Code Playgroud)

  • 这种方法至少适用于Rails 4.2和Postgresql.我已经测试过了,但你需要使用`primary_key:true`而不仅仅是在回答中给出的`primary` (2认同)

小智 8

我在Rails 2.3.5上,我的以下方式适用于SQLite3

create_table :widgets, { :primary_key => :widget_id } do |t|
  t.string :widget_id

  # other column definitions
end
Run Code Online (Sandbox Code Playgroud)

不需要:id => false.