Rails 4. Migrate table id to UUID

后端 未结 3 727
予麋鹿
予麋鹿 2020-12-05 18:39

I have a table: db/migrate/20140731201801_create_voc_brands.rb:

class CreateVocBrands < ActiveRecord::Migration
  def change
    create_table :vo         


        
相关标签:
3条回答
  • 2020-12-05 19:08

    I had the same problem as yours. To migrate from default id to use uuid, I think you could something similar to what I had:

    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
    
    0 讨论(0)
  • 2020-12-05 19:11

    I know migrations are preferred way to made any db change but below approach is awesome. It is possible to use direct queries to PostgreSQL to convert table with existing data.

    For primary key:

        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()
    

    For other references:

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

    The above left pads the integer value with zeros and converts to a UUID. This approach does not require id mapping and if needed old id could be retrieved.

    As there is no data copying, this approach works quite fast.

    To handle these and more complicated case of polymorphic associations please use https://github.com/kreatio-sw/webdack-uuid_migration. This gem adds additional helpers to ActiveRecord::Migration to ease these migrations.

    0 讨论(0)
  • 2020-12-05 19:17

    I know this doesn't directly answer the question, but I created a rake task that can help convert any project from id to 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
    
    0 讨论(0)
提交回复
热议问题