如何在 ActiveRecord 中从使用一个外键迁移到另一个外键?

How to migrate from using one foreign key to another in ActiveRecord?

使用 Postgres 作为后备存储,我有一个 table 它(至少目前)有一个整数主键和一个具有唯一索引的 uuid。

在我的 schema.rb 中看起来像这样(例如简化):

create_table "regions", force: cascade do |t|
  t.integer  "region_id" 
  t.uuid     "uuid",        default: "uuid_generate_v4()"
  t.string   "name"
end

add_index "regions", ["uuid"], name "index_regions_on_uuid", unique: true, using :btree

然后我有一个 table,它引用了整数 ID,如下所示:

create_table "sites", force:cascade do 
  t.integer  "site_id"
  t.integer  "region_id"
  t.string   "name"
end

我想做的是把第二个table中的外键从region_id切换到uuid。我应该如何写这个迁移?

只需创建一个迁移,并向其中注入一些 SQL 魔法:

def up
  # Create and fill in region_uuid column,
  # joining records via still existing region_id column
  add_column :sites, :region_uuid

  if Site.reflect_on_association(:region).foreign_key == 'region_id'
    # We won't use 'joins(:regions)' in case we will need
    # to re-run migration later, when we already changed association
    # code as suggested below. Specifying join manually instead.
    Site.joins("INNER JOIN regions ON site.region_id = regions.id").update_all("region_uuid = regions.uuid")
  end

  drop_column :sites, :region_id
end

那么你只需要修复你的关联:

class Site < ActiveRecord::Base
  belongs_to :region, primary_key: :uuid, foreign_key: :region_uuid
end

class Region < ActiveRecord::Base
  has_many :sites, primary_key: :uuid, foreign_key: :region_uuid
end

从您的评论来看,您似乎想修改关联引用的主键,而不是外键。您实际上不需要迁移来执行此操作。相反,只需在每个模型的关联定义中指定主键:

Class Region << ActiveRecord::Base
  has_many :sites, primary_key: :uuid
end

Class Site << ActiveRecord::Base
  belongs_to :region, primary_key: :uuid
end

外键,因为它遵循 rails 命名为 belongs_to 关系并附加 "_id"(在本例中为 region_id)的约定,所以此处无需指定。

ETA: 您还需要确保 sites.region_id 的类型与 regions.uuid 的类型匹配,我假设是 uuid.我还假设该字段之前已编入索引(根据 ActiveRecord 约定)并且您仍然希望它编入索引。您可以像这样在迁移中更改所有这些:

def up
  remove_index :sites, :region_id
  change_column :sites, :region_id, :uuid
  add_index :sites, :region_id
end

def down
  remove_index :sites, :region_id
  change_column :sites, :region_id, :integer
  add_index :sites, :region_id
end