ChatGPT解决这个技术问题 Extra ChatGPT

使用 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),而不必在 execute 调用中编写 SQL?

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

我有同样的问题,我很想看到这个问题的答案。到目前为止,这些建议都没有奏效。
如果您使用 Postgres,它们都不起作用。 Dan Chak 的“Enterprise Rails”有一些使用自然/复合键的技巧。
请注意,当您使用以下内容时: <!-- 语言:lang-rb -->执行“ALTER TABLE 员工添加主键(emp_id);”虽然在运行 rake db:migrate 后表约束设置正确,但自动生成的架构定义不包含此约束!

C
Community

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

解决方法将使 rake db:test:clone 或 rake db:schema:load 将 emp_id 创建为整数列。
实际上,现在您应该使用 self.primary_key= 而不是 set_primary_key,因为后者已被弃用。
这是对我有用的 only 解决方案。我希望它可以帮助其他人:stackoverflow.com/a/15297616/679628
这种方法的问题在于,当您从 schema.rb 设置数据库时,emp_id 将再次成为 integer 类型列。 schema.rb 似乎无法以任何方式存储 execute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"
@DonnyKurnia @Tintin81 您可以通过 config.active_record.schema_format 设置将架构转储从 schema.rb 切换到 structure.sql,可以是 :sql:rubyguides.rubyonrails.org/v3.2.13/…
A
Austin

这有效:

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 足够聪明,知道迁移时该怎么做?
对于向下迁移:drop_table :employees
不幸的是,这个方法也给我们留下了一个不同步的 schema.rb ...它会保留 :primary_key => :emp_id 声明,但是当 rake db:schema:load 被调用时,就像在测试开始时一样,它会产生一个整数列。但是,如果您切换到使用 structure.sql(配置选项),测试可能会保留设置并使用该文件来加载架构。
S
Sean McCleary

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


P
Pavel Chuchuva

在 Rails 5 中你可以做

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

请参阅create_table documentation


值得注意的是,您需要在模型中添加某种 before_create :set_id 方法来分配主键的值
A
Arup Rakshit

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

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

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

但问题是,生成的 schema.rb 文件不反映主键的 string 类型,因此在使用 load 生成数据库时,主键被创建为整数。
p
paulthenerd

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

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

这对我不起作用,至少在 PostgreSQL 中不起作用。根本没有指定主键。
嗯,有趣的是,我已经在 Rails 2.3.2 上使用 MySQL 对其进行了测试——也许也与 rails 的版本有关?
我不确定——我不认为这是 Rails 的版本。我正在使用 Rails 2.3.3。
不幸的是,使用这种方法没有为表设置实际的主键。
这种方法至少适用于 Rails 4.2 和 Postgresql。我已经测试过了,但您需要使用 primary_key: true 而不是仅在答案中给出的 primary
T
Trung LE

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

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

  # other column definitions
end

不需要:id => false。


抱歉,但是当我应用您的技术时,widget_id 更改为整数...您有任何解决方案吗?
这不再有效,至少在 ActiveRecord 4.1.4 中无效。它给出以下错误:you can't redefine the primary key column 'widgets'. To define a custom primary key, pass { id: false } to create_table.
G
Giles Bowkett

在几乎每个解决方案都说“这在 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 .


对 FUD 和不准确性投反对票。自 2007 年以来,我在我的大部分 Rails 项目中都使用了 Postgres。Rails 中的 Postgres 支持非常好,并且模式转储程序没有问题。
一方面,Postgres 确实不是这里的问题。另一方面,这是 2009 年的仅 pg 架构转储程序错误 rails.lighthouseapp.com/projects/8994/tickets/2418,这是另一个 rails.lighthouseapp.com/projects/8994/tickets/2514
Rails 和 Postgres 围绕 Postgres 8.3 更新存在一个决定性问题,他们(正确地,IMO)决定转换为字符串文字和引用的 SQL 标准。 Rails 适配器没有检查 Postgres 版本,因此不得不进行一段时间的猴子补丁。
@贾德森很有趣。我从来没有遇到过。
s
shicholas

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

C
Clark

在 Rails 3 和 MySQL 上对我有用的技巧是:

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

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

所以:

使用 :id => false 以免生成整数主键 使用所需的数据类型,并添加 :null => false 在该列上添加唯一索引

似乎 MySQL 将非空列上的唯一索引转换为主键!


在带有 MySQL 5.5.15 的 Rails 3.22 中,它只创建一个唯一键,而不是主键。
B
BvuRVKyUVlViVIc7

你必须使用选项 :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

这对我不起作用,至少在 PostgreSQL 中不起作用。根本没有指定主键。
这种方法将省略 id 列,但实际上不会在表上设置主键。
V
Vijay Sali

这个解决方案怎么样,

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

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

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


e
engineerDave

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

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

Ruby Object Mapper (DataMapper 2) 有很多承诺,并且也建立在 AREL 原则之上!


G
Guster

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