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

时间:2015-08-06 16:58:19

标签: ruby-on-rails ruby postgresql activerecord database-migration

使用Postgres作为后备存储,我有一个表(至少暂时)有一个整数主键和一个具有唯一索引的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

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

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

我想要做的是从region_id切换到uuid作为第二个表中的外键。我应该如何写这个迁移呢?

2 个答案:

答案 0 :(得分:2)

只需创建一个迁移,并吸入一些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

答案 1 :(得分:2)

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

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

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

外键,因为它跟随被称为belongs_to关系的rails约定与附加的"_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