Created
March 25, 2025 19:27
-
-
Save voltechs/92b69fc5422ba5f9b27379d70eb12057 to your computer and use it in GitHub Desktop.
Rails Migration from `bigint` to `uuid`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class AddUuidToAllTables < ActiveRecord::Migration[7.0] | |
TABLES = [ | |
:users, | |
:posts, | |
# list tables | |
] | |
def up | |
TABLES.each do |table| | |
add_column table, :pk_uuid, :uuid, default: "gen_random_uuid()", null: false | |
add_index table, :pk_uuid, unique: true | |
end | |
end | |
def down | |
TABLES.each do |table| | |
remove_column table, :pk_uuid | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ChangePkInTablesWithBelongsToRelationsOnly < ActiveRecord::Migration[7.0] | |
TABLES = [ | |
:users, | |
:posts, | |
# list tables | |
] | |
def up | |
TABLES.each do |table| | |
rename_column table, :id, :numeric_id | |
rename_column table, :pk_uuid, :id | |
change_pk(table) | |
end | |
end | |
def down | |
TABLES.each do |table| | |
rename_column table, :id, :pk_uuid | |
rename_column table, :numeric_id, :id | |
change_pk(table) | |
end | |
end | |
def change_pk(table) | |
execute "ALTER TABLE #{table} DROP CONSTRAINT #{table}_pkey CASCADE;" | |
execute "ALTER TABLE #{table} ADD PRIMARY KEY (id);" | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class UpdateUuidFkValues < ActiveRecord::Migration[7.1] | |
TABLES_WITH_RELATIONS = { | |
posts: { | |
users: { user_id: :user_uuid }, | |
}, | |
} | |
def up | |
TABLES_WITH_RELATIONS.each do |parent_table, child_mappings| | |
child_mappings.each do |child_table, fk_map| | |
fk_map.each do |old_fk_sym, new_fk_sym| | |
# Build the update query to assign the proper UUID from the parent | |
execute <<-SQL.strip_heredoc | |
UPDATE #{child_table} | |
SET #{new_fk_sym} | |
= ( | |
SELECT #{parent_table}.id | |
FROM #{parent_table} | |
WHERE #{parent_table}.numeric_id = #{child_table}.#{old_fk_sym} | |
) | |
SQL | |
end | |
end | |
end | |
end | |
def down | |
# no-op since data cannot be rolled back | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class RenameUuidFkColumnsToOldName < ActiveRecord::Migration[7.1] | |
TABLES_WITH_RELATIONS = { | |
posts: { | |
users: { user_id: :user_uuid }, | |
}, | |
} | |
def up | |
TABLES_WITH_RELATIONS.each do |table, related_tables| | |
# Remove existing foreign keys on child tables safely. | |
remove_foreign_keys(related_tables) | |
# Migrate foreign keys for each child table referencing the given table. | |
related_tables.each do |child_table, mapping| | |
mapping.each do |old_fk_sym, new_fk_sym| | |
old_fk_name = old_fk_sym.to_s # e.g. "user_id" | |
uuid_fk_name = new_fk_sym.to_s # e.g. "user_uuid" | |
numeric_fk_name = "numeric_#{old_fk_name}".to_sym # e.g. :numeric_user_id | |
change_column_null child_table, old_fk_name, true | |
# Rename the existing FK column to a temporary numeric column if not done already. | |
unless column_exists?(child_table, numeric_fk_name) | |
rename_column child_table, old_fk_name, numeric_fk_name | |
end | |
# If the UUID column exists and the "old" column is not present, rename it. | |
if column_exists?(child_table, uuid_fk_name) && !column_exists?(child_table, old_fk_name) | |
rename_column child_table, uuid_fk_name, old_fk_name | |
end | |
add_foreign_key child_table, table, column: old_fk_name | |
# Now that the FK is linked, drop the temporary numeric column. | |
if column_exists?(child_table, numeric_fk_name) | |
remove_column child_table, numeric_fk_name | |
end | |
end | |
end | |
end | |
end | |
def down | |
TABLES_WITH_RELATIONS.each do |table, related_tables| | |
# Remove foreign keys from child tables safely. | |
remove_foreign_keys(related_tables) | |
# Rollback FK migration for each child table. | |
related_tables.each do |child_table, mapping| | |
mapping.each do |old_fk_sym, new_fk_sym| | |
old_fk_name = old_fk_sym.to_s | |
uuid_fk_name = new_fk_sym.to_s | |
numeric_fk_name = "numeric_#{old_fk_name}".to_sym | |
# Reverse the renaming. | |
if column_exists?(child_table, old_fk_name) && !column_exists?(child_table, uuid_fk_name) | |
rename_column child_table, old_fk_name, uuid_fk_name | |
end | |
# Since the numeric column was dropped in up, re-create it if needed. | |
unless column_exists?(child_table, numeric_fk_name) | |
add_column child_table, numeric_fk_name, :bigint | |
end | |
if column_exists?(child_table, numeric_fk_name) && !column_exists?(child_table, old_fk_name) | |
rename_column child_table, numeric_fk_name, old_fk_name | |
end | |
add_foreign_key child_table, table, column: old_fk_name | |
change_column_null child_table, old_fk_name, false | |
end | |
end | |
end | |
end | |
# Helper that checks if a foreign key exists before attempting its removal. | |
def remove_foreign_keys(related_tables) | |
related_tables.each do |child_table, mapping| | |
mapping.each do |old_fk_sym, _| | |
column_name = old_fk_sym.to_s | |
fks = ActiveRecord::Base.connection.foreign_keys(child_table) | |
if fks.map(&:column).include?(column_name) | |
remove_foreign_key child_table, column: column_name | |
end | |
end | |
end | |
end | |
def change_pk(table) | |
execute "ALTER TABLE #{table} DROP CONSTRAINT #{table}_pkey;" | |
execute "ALTER TABLE #{table} ADD PRIMARY KEY (id);" | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require_relative '../config/environment' | |
require 'active_record' | |
require 'active_support/inflector' | |
# Ensure Rails is loaded appropriately so that ActiveRecord::Base.connection is available. | |
# For example, you might need to load config/environment if running stand-alone. | |
# require_relative '../config/environment' | |
def build_fk_mapping | |
mapping = {} | |
# Iterate over every table in the current connection. | |
ActiveRecord::Base.connection.tables.each do |child_table| | |
fks = ActiveRecord::Base.connection.foreign_keys(child_table) | |
fks.each do |fk| | |
# Use the actual referenced table from the FK definition. | |
referenced_table = fk.to_table.to_sym | |
mapping[referenced_table] ||= {} | |
mapping[referenced_table][child_table] ||= {} | |
old_fk = fk.column.to_sym | |
# Derive new FK column name. If the existing column ends in "_id", replace it with "_uuid". | |
new_fk = if fk.column.to_s.end_with?('_id') | |
fk.column.to_s.sub(/_id$/, '_uuid').to_sym | |
else | |
"#{fk.column}_uuid".to_sym | |
end | |
mapping[referenced_table][child_table][old_fk] = new_fk | |
end | |
end | |
mapping.reject { |_, child_mapping| child_mapping.empty? } | |
end | |
def generate_relations_output(mapping) | |
puts "TABLES_WITH_RELATIONS = {" | |
mapping.each do |referenced_table, child_mapping| | |
puts " #{referenced_table}: {" | |
child_mapping.each do |child_table, fk_map| | |
mapping_str = fk_map.map { |old_fk, new_fk| "#{old_fk}: :#{new_fk}" }.join(", ") | |
puts " #{child_table}: { #{mapping_str} }," | |
end | |
puts " }," | |
end | |
puts "}" | |
end | |
# ------------------------------------------------------------------- | |
fk_mapping = build_fk_mapping | |
generate_relations_output(fk_mapping) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment