Rails 4.将表id迁移到UUID

时间:2014-09-05 08:27:37

标签: ruby-on-rails migration rails-migrations

我有一张桌子: 分贝/迁移/ 20140731201801_create_voc_brands.rb:

class CreateVocBrands < ActiveRecord::Migration
  def change
    create_table :voc_brands do |t|
      t.string :name

      t.timestamps
    end
  end
end

但我需要将表更改为此(如果我将从零创建它):

class CreateVocBrands < ActiveRecord::Migration
  def change
    create_table :voc_brands, :id => false do |t|
      t.uuid :id, :primary_key => true
      t.string :name

      t.timestamps
    end
    add_index :voc_brands, :id
  end
end

如何使用迁移更改此内容?

4 个答案:

答案 0 :(得分:28)

我和你的问题一样。要从默认ID迁移到使用uuid,我认为你可以使用类似于我的东西:

class ChangeVocBrandsPrimaryKey < ActiveRecord::Migration
  def change
    add_column :voc_brands, :uuid, :uuid, default: "uuid_generate_v4()", null: false

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

答案 1 :(得分:3)

我知道迁移是进行任何数据库更改的首选方法,但是下面的方法非常棒。可以对PostgreSQL使用直接查询来转换表与现有数据。

主键:

    ALTER TABLE students
        ALTER COLUMN id DROP DEFAULT,
        ALTER COLUMN id SET DATA TYPE UUID USING (uuid(lpad(replace(text(id),'-',''), 32, '0'))),
        ALTER COLUMN id SET DEFAULT uuid_generate_v4()

其他参考资料:

    ALTER TABLE students
        ALTER COLUMN city_id SET DATA TYPE UUID USING (uuid(lpad(replace(text(city_id),'-',''), 32, '0')))

上面左边用零填充整数值并转换为UUID。这种方法不需要id映射,如果需要,可以检索旧id。

由于没有数据复制,这种方法非常快。

要处理这些更复杂的多态关联案例,请使用https://github.com/kreatio-sw/webdack-uuid_migration。此gem为ActiveRecord :: Migration添加了额外的帮助程序,以简化这些迁移。

答案 2 :(得分:0)

我知道这不能直接回答问题,但是我创建了一个rake任务,可以帮助将任何项目从id转换为uuid https://gist.github.com/kuczmama/152d762177968f7192df1dea184e3370

task id_to_uuid: :environment do
  puts "[START] Convert id to uuid"
  ActiveRecord::Base.connection.enable_extension 'uuid-ossp' unless ActiveRecord::Base.connection.extensions.include? 'uuid-ossp'
  ActiveRecord::Base.connection.enable_extension 'pgcrypto' unless ActiveRecord::Base.connection.extensions.include? 'pgcrypto'

  table_names = ActiveRecord::Base.connection.tables - ["schema_migrations", "ar_internal_metadata", "migration_validators"]
  table_names.each do |table_name|
    puts "[CREATE] uuid column for #{table_name}"

    #Make sure the column is a uuid if not delete it and then create it
    if ActiveRecord::Migration.column_exists? table_name, :uuid
      column_type = ActiveRecord::Migration.columns(table_name).select{|c| c.name == "uuid"}.try(:first).try(:sql_type_metadata).try(:type)
      if column_type && column_type != :uuid
        ActiveRecord::Migration.remove_column(table_name, :uuid)
      end
    end

    # Create it if it doesn't exist
    if !ActiveRecord::Migration.column_exists? table_name, :uuid
      ActiveRecord::Migration.add_column table_name, :uuid, :uuid, default: "uuid_generate_v4()", null: false
    end

  end

  # The strategy here has three steps.
  # For each association:
  # 1) write the association's uuid to a temporary foreign key _uuid column,
  # 2) For each association set the value of the _uuid column
  # 3) remove the _id column and
  # 4) rename the _uuid column to _id, effectively migrating our foreign keys to UUIDs while sticking with the _id convention.
  table_names.each do |table_name|
    puts "[UPDATE] change id to uuid #{table_name}"
    model = table_name.singularize.camelize.constantize
    id_columns = model.column_names.select{|c| c.end_with?("_id")}


    # write the association's uuid to a temporary foreign key _uuid column
    # eg. Message.room_id => Message.room_uuid
    model.reflections.each do|k, v|
      begin
        association_id_col = v.foreign_key
        # Error checking
        # Make sure the relationship actually currently exists
        next unless id_columns.include?(association_id_col)
        # Check that there is at

        # 1) Create temporary _uuid column set to nulll,
        tmp_uuid_column_name = column_name_to_uuid(association_id_col)
        unless ActiveRecord::Migration.column_exists?(table_name, tmp_uuid_column_name)
          puts "[CREATE] #{table_name}.#{tmp_uuid_column_name}"
          ActiveRecord::Migration.add_column(table_name, tmp_uuid_column_name, :uuid)
        end

        # 2) For each association set the value of the _uuid column
        #
        # For example.  Assume the following example
        #
        # message.room_id = 1
        # room = Room.find(1)
        # room.uuid = 0x123
        # message.room_uuid = 0x123
        #
        association_klass = v.klass

        model.unscoped.all.each do |inst|
          next unless inst.present?
          association = association_klass.find_by(id: inst.try(association_id_col.try(:to_sym)))
          next unless association.present?
          inst.update_column(tmp_uuid_column_name, association.try(:uuid))
        end

        # 3) Remove id column
        ActiveRecord::Migration.remove_column table_name, association_id_col if ActiveRecord::Migration.column_exists?(table_name, association_id_col)

        # 4) Rename uuid_col_name to id
        ActiveRecord::Migration.rename_column table_name, tmp_uuid_column_name, association_id_col
      rescue => e
        puts "Error: #{e} continuing"
        next
      end
    end

    # Make each temp _uuid column linked up
    # eg. Message.find(1).room_uuid = Message.find(1).room.uuid
    puts "[UPDATE] #{model}.uuid to association uuid"
  end

  ## Migrate primary keys to uuids
  table_names.each do |table_name|
    if ActiveRecord::Migration.column_exists?(table_name, :id) && ActiveRecord::Migration.column_exists?(table_name, :uuid)
      ActiveRecord::Base.connection.execute %Q{ALTER TABLE #{table_name} DROP CONSTRAINT #{table_name}_pkey CASCADE} rescue nil
      ActiveRecord::Migration.remove_column(table_name, :id)
      ActiveRecord::Migration.rename_column( table_name, :uuid, :id) if ActiveRecord::Migration.column_exists?(table_name, :uuid)
      ActiveRecord::Base.connection.execute "ALTER TABLE #{table_name} ADD PRIMARY KEY (id)"
      ActiveRecord::Base.connection.execute %Q{DROP SEQUENCE IF EXISTS #{table_name}_id_seq CASCADE} rescue nil
    end
  end
end

# Add uuid to the id
# EG. column_name_to_uuid("room_id") => "room_uuid"
# EG. column_name_to_uuid("room_ids") => "room_uuids"
def column_name_to_uuid(column_name)
  *a, b = column_name.split("_id", -1)
  a.join("_id") + "_uuid" + b
end

答案 3 :(得分:0)

如果有人来这里寻找如何从 UUID 转换为整数 ID,您可以使用以下迁移:

The formula for this should be (cell value) / ((sum [ExpenseA] til [ExpenseC])/10)