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

时间:2009-07-29 14:15:20

标签: ruby-on-rails database migration 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

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

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

14 个答案:

答案 0 :(得分: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

因此,我们可以看到,当您尝试在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 mentioned,您的ActiveRecord模型应使用set_primary_key设置主键:

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

答案 1 :(得分: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

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

答案 2 :(得分: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);"

在你的模型中这样做:

class User < ActiveRecord::Base
    set_primary_key :oid
    ...
end

答案 3 :(得分: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

查看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=#

从Rails控制台:

Loading development environment (Rails 4.2.1)
=> Unable to load pry
>> Track.primary_key
=> "apple_id"
>>

答案 4 :(得分: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

这将使列widget_id成为Widget类的主键,然后由您在创建对象时填充该字段。您应该可以使用之前的创建回调来执行此操作。

这就像

那样
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

答案 5 :(得分:8)

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

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

  # other column definitions
end

不需要:id =&gt;假的。

答案 6 :(得分:8)

在Rails 5中你可以做到

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

请参阅create_table documentation

答案 7 :(得分:4)

几乎所有的解决方案都说“这对我在X数据库上起作用”之后,我看到原始海报对“在Postgres上对我不起作用”效果的评论。事实上,这里真正的问题可能是Rails中的Postgres支持,这种支持并非完美无缺,并且在2009年这个问题最初发布时可能更糟。例如,如果我没记错的话,如果你使用Postgres,你基本上无法从rake db:schema:dump获得有用的输出。

我自己不是Postgres忍者,我从Postgres的Xavier Shay优秀的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。

答案 8 :(得分:4)

我找到了一个适用于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

答案 9 :(得分:3)

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

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

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

所以:

  1. 使用:id =&gt; false以便不生成整数主键
  2. 使用所需的数据类型,并添加:null =&gt;假
  3. 在该列上添加唯一索引
  4. 似乎MySQL将非空列上的唯一索引转换为主键!

答案 10 :(得分:2)

您必须使用以下选项:id =&gt;假

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

答案 11 :(得分:1)

这个解决方案怎么样,

Inside Employee模型为什么我们不能添加将检查coloumn中唯一性的代码,例如:假设Employee是Model,因为你有EmpId,这是字符串然后我们可以添加“:uniqueness =&gt ;真实“到EmpId

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

我不确定这是否是解决办法,但这对我有用。

答案 12 :(得分:1)

我知道这是一个我偶然发现的旧线程...但我有点震惊没人提到DataMapper。

我发现如果你需要偏离ActiveRecord惯例,我发现它是一个很好的选择。此外,它是一种更好的遗留方法,您可以“按原样”支持数据库。

Ruby Object Mapper(DataMapper 2)也承担了许多承诺并建立在AREL原则之上!

答案 13 :(得分:1)

添加索引对我有用,我使用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