It's totally doable, and in many cases it is a good design choice. Creating a globally unique ID makes things much easier when sharding, with multi-master writes, or when needing to move to any multi-database system. The downside, of course, is that it takes up twice the space of a bigint, and it's more complex than auto-increment.
First, NEVER STORE UUIDs AS VARCHAR. OK, now that's out of the way, here's some code:
This gem uses Mysql 8.0+'s UUID_TO_BIN and BIN_TO_UUID to store binary and retrieve UUID characters. https://github.com/nedap/mysql-binuuid-rails
gem 'mysql-binuuid-rails'
In the model, use the Attributes API from Rails 5+. Be sure to remember to set all foreign keys to decode the UUID, i.e. attribute :user_id, MySQLBinUUID::Type.new
class Account < ApplicationRecord
attribute :id, MySQLBinUUID::Type.new
before_create { self.id = ApplicationRecord.generate_uuid }
end
This migration specifies not to create the default primary key, and then manually specifies the primary key. The trigger is nice because if you run any queries that create records without ActiveRecord, it'll still generate and populate the primary keys correctly.
class CreateAccounts < ActiveRecord::Migration[6.0]
def change
create_table :accounts, id: false do |t|
t.binary :id, limit: 16, primary_key: true
t.string :email
t.timestamps
end
reversible do |dir|
dir.up do
execute <<~SQL
CREATE TRIGGER before_insert_set_account_uuid
BEFORE INSERT ON accounts
FOR EACH ROW
BEGIN
IF new.id IS NULL THEN
SET new.id = UUID_TO_BIN(UUID(), 1);
END IF;
END
SQL
execute "UPDATE accounts SET id = UUID_TO_BIN(UUID());"
end
dir.down do
execute <<~SQL
DROP TRIGGER before_insert_set_account_uuid;
SQL
end
end
end
end
This is using MySQL's method to create a UUID. Note that this means two round-trips to the database, one to fetch the new UUID and one to save the record. It may be better to leverage UUIDTools etc. to generate it locally.
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def self.generate_uuid
ActiveRecord::Base.connection.execute("select UUID();").first[0]
end
end