Ma réponse est une mise à jour de la réponse de @santosh. J'intègre toutes les bonnes pratiques décrites ici :
- https://www.percona .com/blog/2014/12/19/store-uuid-optimized-way/
- http://mysqlserverteam.com/storing-uuid-values -in-mysql-tables/
J'utilise le simple_uuid
gem car il peut générer des UUID "v1". SecureRandom.uuid
intégré à Ruby génère v4. Nous avons besoin de v1, car c'est ce qui intègre l'horodatage dans le cadre de l'UUID. Lisez les liens ci-dessus pour mieux comprendre. UUID()
de MySQL la fonction génère des UUID v1.
app/models/concerns/binary_uuid_pk.rb
module BinaryUuidPk
extend ActiveSupport::Concern
included do
before_validation :set_id, on: :create
validates :id, presence: true
end
def set_id
uuid_object = SimpleUUID::UUID.new
uuid_string = ApplicationRecord.rearrange_time_of_uuid( uuid_object.to_guid )
uuid_binary = ApplicationRecord.id_binary( uuid_string )
self.id = uuid_binary
end
def uuid
self[:uuid] || (id.present? ? ApplicationRecord.format_uuid_with_hyphens( id.unpack('H*').first ).upcase : nil)
end
module ClassMethods
def format_uuid_with_hyphens( uuid_string_without_hyphens )
uuid_string_without_hyphens.rjust(32, '0').gsub(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, '\1-\2-\3-\4-\5')
end
def rearrange_time_of_uuid( uuid_string )
uuid_string_without_hyphens = "#{uuid_string[14, 4]}#{uuid_string[9, 4]}#{uuid_string[0, 8]}#{uuid_string[19, 4]}#{uuid_string[24..-1]}"
ApplicationRecord.format_uuid_with_hyphens( uuid_string_without_hyphens )
end
def id_binary( uuid_string )
# Alternate way: Array(uuid_string.downcase.gsub(/[^a-f0-9]/, '')).pack('H*')
SimpleUUID::UUID.new( uuid_string ).to_s
end
def id_str( uuid_binary_string )
SimpleUUID::UUID.new( uuid_binary_string ).to_guid
end
# Support both binary and text as IDs
def find( *ids )
ids = [ids] unless ids.is_a?( Array )
ids = ids.flatten
array_binary_ids = ids.each_with_object( [] ) do |id, array|
case id
when Integer
raise TypeError, 'Expecting only 36 character UUID strings as primary keys'
else
array << SimpleUUID::UUID.new( id ).to_s
end
end
super( array_binary_ids )
end
end
end
app/models/application_record.rb
## ApplicationRecord (new parent of all models in Rails 5)
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
include BinaryUuidPk
end
Désormais, tous les modèles prendront en charge les clés primaires UUID optimisées.
Exemple de migration
class CreateUserProfiles < ActiveRecord::Migration[5.0]
def change
create_table :user_profiles, id: false do |t|
t.binary :id, limit: 16, primary_key: true, null: false
t.virtual :uuid, type: :string, limit: 36, as: "insert( insert( insert( insert( hex(id),9,0,'-' ), 14,0,'-' ), 19,0,'-' ), 24,0,'-' )"
t.index :uuid, unique: true
t.string :name, null: false
t.string :gender, null: false
t.date :date_of_birth
t.timestamps null: false
end
execute <<-SQL
CREATE TRIGGER before_insert_user_profiles
BEFORE INSERT ON user_profiles
FOR EACH ROW
BEGIN
IF new.id IS NULL THEN
SET new.id = UUID_TO_BIN(uuid(), 1);
END IF;
END
SQL
end
end
Ajouter UUID_TO_BIN()
fonction à la base de données MySQL :
DELIMITER //
CREATE FUNCTION UUID_TO_BIN(string_uuid BINARY(36), swap_flag INT)
RETURNS BINARY(16)
LANGUAGE SQL DETERMINISTIC CONTAINS SQL SQL SECURITY INVOKER
RETURN
UNHEX(CONCAT(
SUBSTR(string_uuid, 15, 4),
SUBSTR(string_uuid, 10, 4),
SUBSTR(string_uuid, 1, 8),
SUBSTR(string_uuid, 20, 4),
SUBSTR(string_uuid, 25) ));
//
DELIMITER ;
La fonction ci-dessus est intégrée à MySQL 8.0 et supérieur. Au moment de la rédaction, 8.0 n'est pas encore GA. Donc, j'ajoute la fonction pour l'instant. Mais j'ai gardé la même signature de fonction que ce qui existe dans MySQL 8.0 . Ainsi, lorsque nous passerons à la version 8.0, toutes nos migrations et tous nos déclencheurs fonctionneront toujours.