Rails迁移 - 暂时忽略外键约束?

问题描述 投票:1回答:3

我正在尝试将表格的id字段更改为uuid

这是我的代码:

class AddUuidToProjects < ActiveRecord::Migration[5.0]
  def up
    add_column :projects, :uuid, :string, limit:36, null: false, first: true
    add_column :projects, :old_id, :integer

    Project.all.each do |p|
      p.update!(old_id: p.id)
    end
    change_table :projects do |t|
      t.remove :id
      t.rename :uuid, :id
    end
    execute "ALTER TABLE projects ADD PRIMARY KEY (id);"

    Project.all.each do |p|
      # has_one image
      Image.find(p.old_id).update!(project: p) 
      # has_many stories
      Story.where(project_id: p.old_id).each do |s|
        s.update!(project: p)
      end
    end
  end
  ...
end

由于外键约束,尝试t.remove :id时此迁移会中断。错误消息是:

Mysql2::Error: Cannot drop column 'id': needed in a foreign key constraint 'fk_rails_be41fd4bb7' of table 'db_dev.stories': ALTER TABLE `projects` DROP `id`

问题是,如果整个迁移运行,那么我将把id列与另一个进行交换,并修复外键。那么,有没有办法忽略迁移的约束?

mysql ruby-on-rails foreign-keys ruby-on-rails-5 rails-migrations
3个回答
3
投票

ActiveRecord::Base.connection.disable_referential_integrityGithub)。至少需要Rails 4.2。

附:

我发现上面的代码很容易出错。我的主要原因是缺乏对这项关键任务的测试。 “Thoughtbot”中有一个很棒的article关于将数据迁移与结构迁移混合在一起的其他问题。

我会考虑创建单独的数据迁移(当然,还包括测试)。 Example


1
投票

我的最终代码

ifunless声明是因为我正在逐步编写和测试(失败的迁移仍然具有持久影响)。最主要的是删除外键然后将它们添加回去(删除键不会删除数据库中的id字段,只会删除约束。

class AddUuidToProjects < ActiveRecord::Migration[5.0]
  def up
    # remove constraint
    if foreign_key_exists?(:stories, :projects)
      say("removing foreign key constraints")

      remove_foreign_key "stories", "projects"
      remove_foreign_key "images", "projects"
    end

    # create UUID id column
    unless column_exists?(:projects, :id, :string)
      say("adding UUID column")

      add_column :projects, :uuid, :string, limit:36, null: false, first: true
      add_column :projects, :old_id, :integer

      Project.all.each do |p|
        p.update!(old_id: p.id, uuid: SecureRandom.uuid)
      end

      change_table :projects do |t|
        t.remove :id
        t.rename :uuid, :id
      end
      execute "ALTER TABLE projects ADD PRIMARY KEY (id);"
    end

    # update foreign keys
    if(Image.first.project_id.is_a? Integer)
      say("updating foreign keys")

      # change foreign key fields to STRING(36)
      change_column :images, :project_id, :string, limit:36, null: false
      change_column :stories, :project_id, :string, limit:36, null: false

      Project.all.each do |p|
        # has_one soi
        Image.find_by(project: p.old_id).update!(project: p)

        # has_many stories
        Snippet.where(project_id: p.old_id).each do |s|
          s.update!(project: p)
        end
      end
    end

    # add constraints back
    unless foreign_key_exists?(:stories, :projects)
      say("adding foreign key constraints back")

      add_foreign_key "stories", "projects"
      add_foreign_key "images", "projects"
    end
  end

0
投票

在mysql上,你可以这样做:

begin
  ActiveRecord::Base.connection.execute 'SET FOREIGN_KEY_CHECKS=0;'
  # Your statements
  ...
ensure
  ActiveRecord::Base.connection.execute 'SET FOREIGN_KEY_CHECKS=1;'
end
© www.soinside.com 2019 - 2024. All rights reserved.