Skip to content

Instantly share code, notes, and snippets.

@jakecraige
Created August 7, 2015 22:46
Show Gist options
  • Save jakecraige/f1a1bffef714be2b61fb to your computer and use it in GitHub Desktop.
Save jakecraige/f1a1bffef714be2b61fb to your computer and use it in GitHub Desktop.
Example migration migrating from integer IDs as a primary key to uuid IDs while maintaining existing associations.
class ChangeIdIntToUuid < ActiveRecord::Migration
def up
remove_foreign_key "business_debts", "loan_applications"
remove_foreign_key "guarantors", "loan_applications"
remove_foreign_key "guarantors", "users"
table_names = [
:loan_applications,
:business_debts,
:case_owners,
:guarantors,
:document_collections,
:users,
:legacy_users,
]
update_primary_keys(table_names)
update_has_many_associations(table_names)
remove_temporary_columns(table_names)
add_foreign_key "business_debts", "loan_applications"
add_foreign_key "guarantors", "loan_applications"
add_foreign_key "guarantors", "users"
add_index :guarantors, :loan_application_id
add_index :guarantors, :user_id
add_index :business_debts, :loan_application_id
add_index(
:document_collections,
[:documentable_type, :documentable_id, :name, :year],
unique: true,
name: "index_document_collections_by_type",
)
end
def down
raise ActiveRecord::IrreversibleMigration.new(
"This cannot be undone because it's destructive of the original IDs."
)
end
def update_primary_keys(table_names)
table_names.each do |table|
update_primary_key_to_uuid(table)
end
end
def update_primary_key_to_uuid(table_name)
add_column table_name, :temp_uuid, :uuid, default: "uuid_generate_v4()"
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{table_name}_pkey;"
rename_column table_name, :id, "#{original_prefix}id"
rename_column table_name, :temp_uuid, :id
execute "ALTER TABLE #{table_name} ADD PRIMARY KEY (id);"
end
def update_has_many_associations(table_names)
has_many_associations = table_names.map do |table_name|
model_class = constantize_table(table_name)
model_class.reflect_on_all_associations(:has_many)
end.flatten
has_many_associations = has_many_associations.select do |association|
association.instance_of?(ActiveRecord::Reflection::HasManyReflection)
end
has_many_associations = has_many_associations.uniq do |association|
[association.class_name, association.foreign_key]
end
has_many_associations.each do |association|
update_has_many_association(association)
end
end
def update_has_many_association(association)
parent_class = association.active_record
model_class = association.class_name.constantize
table_name = model_class.table_name
foreign_key = association.foreign_key
original_foreign_key = "#{original_prefix}#{foreign_key}"
rename_column table_name, foreign_key, original_foreign_key
add_column table_name, foreign_key, :uuid
parent_class.reset_column_information
model_class.reset_column_information
update_all_model_associations(
model_class: model_class,
parent_class: parent_class,
original_foreign_key: original_foreign_key,
foreign_key: foreign_key
)
end
def update_all_model_associations(
model_class:,
parent_class:,
original_foreign_key:,
foreign_key:
)
model_class.find_each do |instance|
association_id = instance.send(original_foreign_key)
parent_model = parent_class.find_by(
"#{original_prefix}id": association_id
)
if parent_model
instance.update_column(foreign_key, parent_model.id)
end
end
end
def remove_temporary_columns(table_names)
table_names.each do |table_name|
model_class = constantize_table(table_name)
columns_to_remove = model_class.column_names.select do |column|
column.start_with?(original_prefix)
end
remove_columns table_name, *columns_to_remove
end
end
def constantize_table(table_name)
table_name.to_s.singularize.camelize.constantize
end
def original_prefix
"original_"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment