From 13db38464863972c141816c89b07544b096e8b4b Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Wed, 3 Dec 2025 14:11:46 +0000 Subject: [PATCH] Auto default for UUID primary keys Patch load_schmema! to set the default value for UUID primary keys. This removes the need to patch ApplicationRecord + Rails models individually. It also means we no longer need to patch the default in for the integer primary key in Search::Record::SQLite. --- app/models/application_record.rb | 2 - app/models/search/record/sqlite.rb | 2 - config/initializers/uuid_framework_models.rb | 6 +-- config/initializers/uuid_primary_keys.rb | 43 +++++++++++++++++++- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 45e9c2f21..4ed46f2c3 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -2,6 +2,4 @@ class ApplicationRecord < ActiveRecord::Base primary_abstract_class configure_replica_connections - - attribute :id, :uuid, default: -> { ActiveRecord::Type::Uuid.generate } end diff --git a/app/models/search/record/sqlite.rb b/app/models/search/record/sqlite.rb index c3da9131a..ae0d34281 100644 --- a/app/models/search/record/sqlite.rb +++ b/app/models/search/record/sqlite.rb @@ -2,8 +2,6 @@ module Search::Record::SQLite extend ActiveSupport::Concern included do - # Override default UUID id attribute, as FTS5 uses rowid integer primary key - attribute :id, :integer, default: nil attribute :result_title, :string attribute :result_content, :string diff --git a/config/initializers/uuid_framework_models.rb b/config/initializers/uuid_framework_models.rb index c0e900176..3345dd97b 100644 --- a/config/initializers/uuid_framework_models.rb +++ b/config/initializers/uuid_framework_models.rb @@ -1,14 +1,10 @@ -# Inject UUID primary key support into Rails framework models +# Inject account associations into Rails framework models Rails.application.config.to_prepare do - ActionText::RichText.attribute :id, :uuid, default: -> { ActiveRecord::Type::Uuid.generate } ActionText::RichText.belongs_to :account, default: -> { record.account } - ActiveStorage::Attachment.attribute :id, :uuid, default: -> { ActiveRecord::Type::Uuid.generate } ActiveStorage::Attachment.belongs_to :account, default: -> { record.account } - ActiveStorage::Blob.attribute :id, :uuid, default: -> { ActiveRecord::Type::Uuid.generate } ActiveStorage::Blob.belongs_to :account, default: -> { Current.account } - ActiveStorage::VariantRecord.attribute :id, :uuid, default: -> { ActiveRecord::Type::Uuid.generate } ActiveStorage::VariantRecord.belongs_to :account, default: -> { blob.account } end diff --git a/config/initializers/uuid_primary_keys.rb b/config/initializers/uuid_primary_keys.rb index 56ff7f3af..01b54eab5 100644 --- a/config/initializers/uuid_primary_keys.rb +++ b/config/initializers/uuid_primary_keys.rb @@ -1,4 +1,30 @@ -# Automatically use UUID type for all binary(16) columns +# Automatically use UUID type for all binary(16) columns and generate defaults + +module UuidPrimaryKeyDefault + def load_schema! + define_uuid_primary_key_pending_default + super + end + + private + def define_uuid_primary_key_pending_default + if uuid_primary_key? + pending_attribute_modifications << PendingUuidDefault.new(primary_key) + end + rescue ActiveRecord::StatementInvalid + # Table doesn't exist yet + end + + def uuid_primary_key? + table_name && primary_key && schema_cache.columns_hash(table_name)[primary_key]&.type == :uuid + end + + PendingUuidDefault = Struct.new(:name) do + def apply_to(attribute_set) + attribute_set[name] = attribute_set[name].with_user_default(-> { ActiveRecord::Type::Uuid.generate }) + end + end +end module MysqlUuidAdapter extend ActiveSupport::Concern @@ -12,6 +38,20 @@ module MysqlUuidAdapter end end + # Override fetch_type_metadata to preserve UUID type and limit + def fetch_type_metadata(sql_type, extra = "") + if sql_type == "binary(16)" + simple_type = ActiveRecord::ConnectionAdapters::SqlTypeMetadata.new( + sql_type: sql_type, + type: :uuid, + limit: 16 + ) + ActiveRecord::ConnectionAdapters::MySQL::TypeMetadata.new(simple_type, extra: extra) + else + super + end + end + class_methods do def native_database_types @native_database_types_with_uuid ||= super.merge(uuid: { name: "binary", limit: 16 }) @@ -69,6 +109,7 @@ module TableDefinitionUuidSupport end ActiveSupport.on_load(:active_record) do + ActiveRecord::Base.singleton_class.prepend(UuidPrimaryKeyDefault) ActiveRecord::ConnectionAdapters::TableDefinition.prepend(TableDefinitionUuidSupport) end