使用Rails,我怎么能设置我的主键不是一个整型列?

我正在使用Rails迁移来pipe理数据库模式,并且我正在创build一个简单的表,我希望使用非整数值作为主键(特别是一个string)。 为了从我的问题中抽象出来,我们假设有一个employees表,员工用一个字母数字string标识,例如"134SNW"

我已经尝试在这样的迁移中创build表:

 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并继续前进,并使其成为一个整数列。 有没有其他的方式来让轨生成PRIMARY_KEY约束(我使用PostgreSQL)为我,而不必在execute调用中写入SQL?

:我知道最好不要使用string列作为主键,所以请不要只是说添加一个整数主键的答案。 无论如何,我可以加一个,但这个问题仍然有效。

不幸的是,我已经确定不使用execute就不可能做到这一点。

为什么它不起作用

通过检查ActiveRecord源代码,我们可以findcreate_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选项中指定一个主键时,它会创build一个具有指定名称的主键(或者,如果没有指定,则为id )。 它通过调用可以在表定义块中使用的相同方法来执行此操作: primary_key

schema_statements.rb

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

这只是创build一个名为type :primary_key 。 这在标准数据库适配器中设置为以下内容:

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

解决方法

由于我们把它们作为主键types,所以我们必须使用execute来创build一个不是整数的主键(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 

我有一个办法来处理这个。 执行的SQL是ANSI SQL,所以它可能适用于大多数符合ANSI SQL的关系数据库。 我已经testing过这个适用于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 

这工作:

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

这可能不太好,但最终的结果正是你想要的。

看起来有可能使用这种方法:

 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_id成为Widget类的主键,那么在创build对象时由你来填充字段。 你应该可以使用before create callback来做到这一点。

所以,沿着这条路线

 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 

我在Rails 2.3.5和我的以下方式与SQLite3的作品

 create_table :widgets, { :primary_key => :widget_id } do |t| t.string :widget_id # other column definitions end 

没有必要:id => false。

我已经在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参数通常是迁移本机types之一,它是以下之一::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" >> 

经过几乎所有的说“这个在X数据库上为我工作”的解决scheme,我看到原来的海报对“Postgres没有效果”的评论。 这里真正的问题实际上可能是Postgres对Rails的支持,这并不是完美无缺的,在2009年这个问题最初发布的时候可能更糟。 例如,如果我没有记错的话,如果你在Postgres上,你基本上不能从rake db:schema:dump得到有用的输出rake db:schema:dump

我本人不是Postgres忍者,我从Xavier Shay在Postgres的优秀PeepCodevideo中获得了这个信息。 那个video实际上忽略了Aaron Patterson的一个图书馆,我想Texticle,但是我可能记得错了。 但除此之外,这是非常好的。

无论如何,如果你在Postgres上遇到这个问题,看看解决scheme是否在其他数据库中工作。 也许使用rails new来生成一个新的应用程序作为沙箱,或者只是创build类似的东西

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

config/database.yml

如果你可以validation它是Postgres的支持问题,并且你find了一个修补程序,请为Rails提供补丁,或者将修补程序打包成一个gem,因为Rails社区中的Postgres用户群相当大,主要得益于Heroku 。

我find了一个解决scheme,这与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 

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

我在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. 使用所需的数据types,并添加:null => false
  3. 在该列上添加一个唯一的索引

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

在Rails 5中你可以做

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

请参阅create_table文档 。

这个解决scheme如何,

在Employee模型里面,为什么我们不能添加检查coloumn中唯一性的代码,例如:假设Employee是Model,因为你有EmpId这是string,那么我们可以为EmpId添加“:uniqueness => true”

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

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

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

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

Ruby Object Mapper (DataMapper 2)在AREL原则上也有很多承诺和构build!

添加索引为我工作,我用btw的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