I have a table: db/migrate/20140731201801_create_voc_brands.rb:
class CreateVocBrands < ActiveRecord::Migration
def change
create_table :vo
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
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.
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