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

发布于 2024-07-29 17:48:23 字数 571 浏览 3 评论 0原文

我正在使用 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?

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

I'm using Rails migrations to manage a database schema, and I'm creating a simple table where I'd like to use a non-integer value as the primary key (in particular, a string). To abstract away from my problem, let's say there's a table employees where employees are identified by an alphanumeric string, e.g. "134SNW".

I've tried creating the table in a migration like this:

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

What this gives me is what seems like it completely ignored the line t.string :emp_id and went ahead and made it an integer column. Is there some other way to have rails generate the PRIMARY_KEY constraint (I'm using PostgreSQL) for me, without having to write the SQL in an execute call?

NOTE: I know it's not best to use string columns as primary keys, so please no answers just saying to add an integer primary key. I may add one anyway, but this question is still valid.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

末骤雨初歇 2024-08-05 17:48:23

不幸的是,我确定不使用 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

Unfortunately, I've determined it's not possible to do it without using execute.

Why it doesn't work

By examining the ActiveRecord source, we can find the code for create_table:

In 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

So we can see that when you try to specify a primary key in the create_table options, it creates a primary key with that specified name (or, if none is specified, id). It does this by calling the same method you can use inside a table definition block: primary_key.

In schema_statements.rb:

def primary_key(name)
  column(name, :primary_key)
end

This just creates a column with the specified name of type :primary_key. This is set to the following in the standard database adapters:

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

The workaround

Since we're stuck with these as the primary key types, we have to use execute to create a primary key that is not an integer (PostgreSQL's serial is an integer using a sequence):

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);"

And as Sean McCleary mentioned, your ActiveRecord model should set the primary key using set_primary_key:

class Employee < ActiveRecord::Base
  set_primary_key :emp_id
  ...
end
戏舞 2024-08-05 17:48:23

这有效:

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

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

This works:

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

It may not be pretty, but the end result is exactly what you want.

深海夜未眠 2024-08-05 17:48:23

我有一种方法来处理这个问题。 执行的 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

I have one way of handling this. The executed SQL is ANSI SQL so it will likely work on most ANSI SQL compliant relational databases. I have tested that this works for MySQL.

Migration:

create_table :users, :id => false do |t|
    t.string :oid, :limit => 10, :null => false
    ...
end
execute "ALTER TABLE users ADD PRIMARY KEY (oid);"

In your model do this:

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

心不设防 2024-08-05 17:48:23

在 Rails 5 中,您可以

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

参阅 create_table 文档

In Rails 5 you can do

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

See create_table documentation.

离鸿 2024-08-05 17:48:23

我在 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 ,输入 options = {}) 并阅读以下行:

type 参数通常是迁移本机类型之一,为以下类型之一::primary_key、:string、:text、:integer、:float、:decimal、:datetime、 :时间,:日期,:二进制,:布尔值。

我得到了上面的 IDE,正如我所展示的那样。 这是运行此迁移后的表元数据:

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

I have tried it in Rails 4.2. To add your custom primary key, you can write your migration as :

# 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

While looking at the documentation of column(name, type, options = {}) and read the line :

The type parameter is normally one of the migrations native types, which is one of the following: :primary_key, :string, :text, :integer, :float, :decimal, :datetime, :time, :date, :binary, :boolean.

I got the above ides as i have shown. Here is the table meta data after running this migration :

[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=#

And from Rails console :

Loading development environment (Rails 4.2.1)
=> Unable to load pry
>> Track.primary_key
=> "apple_id"
>>
木槿暧夏七纪年 2024-08-05 17:48:23

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

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

It looks like it is possible to do using this approach:

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

That will make the column widget_id the primary key for the Widget class, then it is up to you to populate the field when objects are created. You should be able to do so using the before create callback.

So something along the lines of

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
帝王念 2024-08-05 17:48:23

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

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

  # other column definitions
end

不需要 :id =>; 错误的。

I am on Rails 2.3.5 and my following way works with SQLite3

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

  # other column definitions
end

There is no need for :id => false.

寂寞清仓 2024-08-05 17:48:23

在几乎每个解决方案都说“这对我在 X 数据库上有用”之后,我看到原始发布者的评论,大意是“对我在 Postgres 上不起作用”。 这里真正的问题实际上可能是 Rails 中的 Postgres 支持,它并不是完美无缺的,而且在 2009 年这个问题最初发布时可能更糟。 例如,如果我没记错的话,如果你使用 Postgres,你基本上无法从 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 。

After nearly every solution which says "this worked for me on X database", I see a comment by the original poster to the effect of "didn't work for me on Postgres." The real issue here may in fact be the Postgres support in Rails, which is not flawless, and was probably worse back in 2009 when this question originally posted. For instance, if I remember correctly, if you're on Postgres, you basically can't get useful output from rake db:schema:dump.

I am not a Postgres ninja myself, I got this info from Xavier Shay's excellent PeepCode video on Postgres. That video actually overlooks a library by Aaron Patterson, I think Texticle but I could be remembering wrong. But other than that it's pretty great.

Anyway, if you're running into this problem on Postgres, see if the solutions work in other databases. Maybe use rails new to generate a new app as a sandbox, or just create something like

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

in config/database.yml.

And if you can verify that it is a Postgres support issue, and you figure out a fix, please contribute patches to Rails or package your fixes in a gem, because the Postgres user base within the Rails community is pretty large, mainly thanks to Heroku.

£烟消云散 2024-08-05 17:48:23

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

I found a solution to this that works with Rails 3:

The migration file:

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

And in the employee.rb model:

self.primary_key = :emp_id
一直在等你来 2024-08-05 17:48:23

在 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 将非空列上的唯一索引转换为主键!

The trick that worked for me on Rails 3 and MySQL was this:

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

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

So:

  1. use :id => false so as not to generate an integer primary key
  2. use the desired datatype, and add :null => false
  3. add a unique index on that column

Seems that MySQL converts the unique index on a non null column to a primary key!

时光是把杀猪刀 2024-08-05 17:48:23

你必须使用选项 :id => 错误的

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

you have to use the option :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
一城柳絮吹成雪 2024-08-05 17:48:23

这个解决方案怎么样,

在 Employee 模型内部,为什么我们不能添加代码来检查列中的唯一性,例如:假设 Employee 是模型,因为您有 EmpId,它是字符串,那么我们可以添加 ":uniqueness => true" 到 EmpId

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

我不确定这是否是解决方案,但这对我有用。

How about this solution,

Inside Employee model why can't we add code that will check for uniqueness in coloumn, for ex: Assume Employee is Model in that you have EmpId which is string then for that we can add ":uniqueness => true" to EmpId

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

I am not sure that this is solution but this worked for me.

度的依靠╰つ 2024-08-05 17:48:23

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

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

Ruby 对象映射器 (DataMapper 2) 拥有很多前景,并且也建立在 AREL 原则之上!

I know this is an old thread I stumbled across... but I'm kind of shocked no one mentioned DataMapper.

I find if you need to stray out of the ActiveRecord convention, I've found that it is a great alternative. Also its a better approach for legacy and you can support the database "as-is".

Ruby Object Mapper (DataMapper 2) holds a lot of promise and build on AREL principles, too!

尛丟丟 2024-08-05 17:48:23

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

Adding index works for me, I'm using 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
习惯成性 2024-08-05 17:48:23

当前的 Rails 7(从 Rails 5 开始?)允许开箱即用(请参阅 create_table 的 RDoc):

create_table :employees, primary_key: :emp_id, id: {type: :string, limit: 32} do |t|
  t.string :first_name
  t.string :last_name
end

要引用此字符串主键,您的迁移如下所示

create_table :jobs do |t|
  t.string employee_id, limit: 32
end
add_foreign_key :jobs, :employees, column: :employee_id, primary_key: :emp_id

Current Rails 7 (starting with Rails 5?) allows this out of the box (see RDoc of create_table):

create_table :employees, primary_key: :emp_id, id: {type: :string, limit: 32} do |t|
  t.string :first_name
  t.string :last_name
end

To reference this string primary key your migration looks like

create_table :jobs do |t|
  t.string employee_id, limit: 32
end
add_foreign_key :jobs, :employees, column: :employee_id, primary_key: :emp_id
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文