【问题标题】:Rails: From UUID to IDRails:从 UUID 到 ID
【发布时间】:2018-02-02 13:21:02
【问题描述】:

我创建了一个模型Order,以 UUID 作为主键。 现在,我想将它改回正常的增量整数。

我尝试运行如下迁移:

add_column :orders, :id, :primary_key

但它显然不起作用,因为已经有一个id 列。

我发现了很多关于如何从 id 转到 uuid 的帖子,但没有找到如何反转它。

【问题讨论】:

    标签: ruby-on-rails ruby postgresql migration ruby-on-rails-5


    【解决方案1】:

    这是在 Rails 应用程序中自动将所有 UUID 转换为整数 ID 的迁移。

    更多优化代码见:github.com/westonganger/rails_uuid_to_integer_primary_keys

    class ChangeUuidToInteger < ActiveRecord::Migration::Current
      def change
        ### LOAD ALL MODELS for `.subclasses` method
        Dir.glob(Rails.root.join("app/models/*.rb")).each{|f| require(f) }
    
        id_map = {}
    
        ApplicationRecord.subclasses.each do |outer_klass|
          outer_klass.reset_column_information
    
          if outer_klass.column_for_attribute(outer_klass.primary_key).type == :uuid
            case outer_klass.connection.adapter_name
            when "Mysql2"
              execute "ALTER TABLE #{outer_klass.table_name} DROP PRIMARY KEY;"
            else
              result = outer_klass.connection.execute("
                SELECT ('ALTER TABLE ' || table_schema || '.' || table_name || ' DROP CONSTRAINT ' || constraint_name) as my_query
                FROM information_schema.table_constraints
                WHERE table_name = '#{outer_klass.table_name}' AND constraint_type = 'PRIMARY KEY';")
                  
              sql_drop_constraint_command = result.values[0].first
    
              execute(sql_drop_constraint_command)
            end
    
            rename_column outer_klass.table_name, outer_klass.primary_key, "tmp_old_#{outer_klass.primary_key}"
    
            add_column outer_klass.table_name, outer_klass.primary_key, outer_klass.connection.native_database_types[:primary_key]
    
            outer_klass.reset_column_information
    
            records = outer_klass.all
    
            if outer_klass.column_names.include?("created_at")
              records = records.reorder(created_at: :asc)
            end
    
            id_map[outer_klass] = {}
    
            records.each_with_index do |record, i|
              old_id = record.send("tmp_old_#{outer_klass.primary_key}")
    
              if record.send(outer_klass.primary_key).nil?
                new_id = i+1
                record.update_columns(outer_klass.primary_key => new_id)
              else
                new_id = record.send(outer_klass.primary_key)
              end
    
              id_map[outer_klass][old_id] = new_id
            end
    
            remove_column outer_klass.table_name, "tmp_old_#{outer_klass.primary_key}"
    
            outer_klass.reset_column_information
          end
        end
    
        ApplicationRecord.subclasses.each do |inner_klass|
          inner_klass.reflect_on_all_associations(:belongs_to).each do |reflection|
            if inner_klass.column_for_attribute(reflection.foreign_key).type == :uuid
              if reflection.polymorphic?
                ### POLYMORPHIC BELONGS TO
                
                #null_constraint = inner_klass.columns.find{|x| x.name == reflection.foreign_key }.null
                if inner_klass.connection.index_exists?(inner_klass.table_name, reflection.foreign_key)
                  remove_index inner_klass.table_name, reflection.foreign_key
                end
                rename_column inner_klass.table_name, reflection.foreign_key, "tmp_old_#{reflection.foreign_key}"
                add_column inner_klass.table_name, reflection.foreign_key, :bigint#, null: null_constraint
                add_index inner_klass.table_name, reflection.foreign_key
    
                inner_klass.reset_column_information
                
                id_map.each do |outer_klass, inner_id_map|
                  records = inner_klass
                    .where("#{inner_klass.table_name}.tmp_old_#{reflection.foreign_key} IS NOT NULL")
                    .where("#{reflection.foreign_type}" => outer_klass.name)
    
                  records.each do |record|
                    old_id = record.send("tmp_old_#{reflection.foreign_key}")
    
                    if old_id
                      new_id = inner_id_map[old_id]
    
                      if new_id
                        ### First Update Column ID Value
                        record.update_columns(reflection.foreign_key => new_id)
                      else
                        # Orphan record, we just clear the value
                        record.update_columns(reflection.foreign_key => nil)
                      end
                    end
                  end
                end
    
                ### Then Change Column Type
                remove_column inner_klass.table_name, "tmp_old_#{reflection.foreign_key}"
    
                inner_klass.reset_column_information
    
              elsif id_map[reflection.klass]
                ### DIRECT BELONGS TO
                
                inner_id_map = id_map[reflection.klass]
    
                #null_constraint = inner_klass.columns.find{|x| x.name == reflection.foreign_key }.null
                if inner_klass.connection.index_exists?(inner_klass.table_name, reflection.foreign_key)
                  remove_index inner_klass.table_name, reflection.foreign_key
                end
                rename_column inner_klass.table_name, reflection.foreign_key, "tmp_old_#{reflection.foreign_key}"
                add_column inner_klass.table_name, reflection.foreign_key, :bigint#, null: null_constraint
                add_index inner_klass.table_name, reflection.foreign_key
    
                inner_klass.reset_column_information
    
                records = inner_klass.where("#{inner_klass.table_name}.tmp_old_#{reflection.foreign_key} IS NOT NULL")
    
                records.each do |record|
                  old_id = record.send("tmp_old_#{reflection.foreign_key}")
    
                  if old_id
                    new_id = inner_id_map[old_id]
    
                    if new_id
                      ### First Update Column ID Value
                      record.update_columns(reflection.foreign_key => new_id)
                    else
                      # Orphan record, we just clear the value
                      record.update_columns(reflection.foreign_key => nil)
                    end
                  end
                end
    
                ### Then Change Column Type
                remove_column inner_klass.table_name, "tmp_old_#{reflection.foreign_key}"
    
                inner_klass.reset_column_information
              end
            end
          end
    
          inner_klass.reflect_on_all_associations(:has_and_belongs_to_many).each do |reflection|
            if id_map[reflection.klass]
              inner_id_map = id_map[reflection.klass]
    
              #null_constraint = join_klass.columns.find{|x| x.name == reflection.foreign_key }.null
              if inner_klass.connection.index_exists?(reflection.join_table, reflection.association_foreign_key)
                remove_index reflect.join_table, reflection.association_foreign_key
              end
              rename_column reflect.join_table, reflection.association_foreign_key, "tmp_old_#{reflection.association_foreign_key}"
              add_column reflect.join_table, reflection.association_foreign_key, :bigint
              add_index reflect.join_table, reflection.association_foreign_key
    
              inner_id_map.each do |old_id, new_id|
                if new_id
                  ### First Update Column ID Value
                  execute "UPDATE #{reflection.join_table} SET #{reflection.association_foreign_key} = '#{new_id}' WHERE tmp_old_#{reflection.association_foreign_key} = '#{old_id}'"
                end
              end
    
              execute "DELETE FROM #{reflection.join_table} WHERE tmp_old_#{reflection.association_foreign_key} NOT IN ('#{inner_id_map.values.join("','")}')"
    
              remove_column reflection.join_table, "tmp_old_#{reflection.association_foreign_key}"
    
              #join_klass.reset_column_information
            end
          end
        end
      end
    
    end
    
    

    【讨论】:

      【解决方案2】:

      表格有数据吗?

      如果没有,只需删除旧的 id 列并添加新的:

      remove_column :orders, :id
      add_column :orders, :id, :primary_key
      

      如果是,将id 列重命名为id_deprecated

      rename_column :orders, :id, :id_deprecated
      add_column :orders, :id, :primary_key
      

      并添加您的逻辑来处理其他表中 old_id 的引用。

      【讨论】:

      • 如果还有其他关联,这可能绝非易事。您需要 UUID -> ID 的映射表,然后需要更新 everything
      • 地图不需要另外一张表,id_deprecated id 可以在orders 中使用,然后只需join 和更新其他表即可
      • 如果您在主表中删除了原始 UUID 值,则不会。你将如何匹配其他记录?
      • 答案特别提到rename 而不是remove 在那种情况下
      猜你喜欢
      • 2014-10-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-30
      • 1970-01-01
      • 1970-01-01
      • 2017-06-17
      • 1970-01-01
      相关资源
      最近更新 更多