我正在使用 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?
注意:我知道最好不要使用字符串列作为主键,所以请不要回答只是说添加一个整数主键。无论如何我可能会添加一个,但这个问题仍然有效。
rake db:migrate
后表约束设置正确,但自动生成的架构定义不包含此约束!
不幸的是,我已经确定不使用 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
这有效:
create_table :employees, :primary_key => :emp_id do |t|
t.string :first_name
t.string :last_name
end
change_column :employees, :emp_id, :string
它可能并不漂亮,但最终结果正是您想要的。
drop_table :employees
schema.rb
...它会保留 :primary_key => :emp_id
声明,但是当 rake db:schema:load
被调用时,就像在测试开始时一样,它会产生一个整数列。但是,如果您切换到使用 structure.sql
(配置选项),测试可能会保留设置并使用该文件来加载架构。
我有一种处理方法。执行的 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
在 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
方法来分配主键的值
我已经在 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
生成数据库时,主键被创建为整数。
看起来可以使用这种方法:
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
primary_key: true
而不是仅在答案中给出的 primary
我在 Rails 2.3.5 上,我的以下方式适用于 SQLite3
create_table :widgets, { :primary_key => :widget_id } do |t|
t.string :widget_id
# other column definitions
end
不需要:id => false。
you can't redefine the primary key column 'widgets'. To define a custom primary key, pass { id: false } to create_table.
在几乎每个解决方案都说“这在 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 .
我找到了一个适用于 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
在 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 将非空列上的唯一索引转换为主键!
你必须使用选项 :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
这个解决方案怎么样,
在 Employee 模型中,为什么我们不能在列中添加检查唯一性的代码,例如:假设 Employee 是 Model,因为您有 EmpId,它是字符串,然后我们可以将“:uniqueness => true”添加到 EmpId
class Employee < ActiveRecord::Base
validates :EmpId , :uniqueness => true
end
我不确定这是解决方案,但这对我有用。
我知道这是我偶然发现的一个旧线程......但我有点震惊没有人提到 DataMapper。
我发现如果您需要偏离 ActiveRecord 约定,我发现它是一个很好的选择。它也是一种更好的遗留方法,您可以“按原样”支持数据库。
Ruby Object Mapper (DataMapper 2) 有很多承诺,并且也建立在 AREL 原则之上!
添加索引对我有用,我正在使用 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
self.primary_key=
而不是set_primary_key
,因为后者已被弃用。schema.rb
设置数据库时,emp_id
将再次成为integer
类型列。schema.rb
似乎无法以任何方式存储execute "ALTER TABLE employees ADD PRIMARY KEY (emp_id);"
。config.active_record.schema_format
设置将架构转储从schema.rb
切换到structure.sql
,可以是:sql
或:ruby
。 guides.rubyonrails.org/v3.2.13/…