Call generate to generate a new
UUID. The method returns a string in one of three
formats. The default format is 36 characters long, and contains the 32
hexadecimal octets and hyphens separating the various value parts. The
:compact
format omits the hyphens, while the :urn
format adds the :urn:uuid
prefix.
For example:
uuid = UUID.new 10.times do p uuid.generate end
UUID (universally unique identifier) are guaranteed to be unique across time and space.
A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit sequence number and a 48-bit node identifier.
The time value is taken from the system clock, and is monotonically incrementing. However, since it is possible to set the system clock backward, a sequence number is added. The sequence number is incremented each time the UUID generator is started. The combination guarantees that identifiers created on the same machine are unique with a high degree of probability.
Note that due to the structure of the UUID and the use of sequence number, there is no guarantee that UUID values themselves are monotonically incrementing. The UUID value cannot itself be used to sort based on order of creation.
To guarantee that UUIDs are unique across all machines in the network, the IEEE 802 MAC address of the machine's network interface card is used as the node identifier.
For more information see RFC 4122.
Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time ticks.
Clock multiplier. Converts Time (resolution: seconds) to UUID clock (resolution: 10ns)
Formats supported by the UUID generator.
:default
Produces 36 characters, including hyphens separating the UUID value parts
:compact
Produces a 32 digits (hexadecimal) value with no hyphens
:urn
Adds the prefix urn:uuid:
to the default format
You don't have to use this, it's just a good default.
MAC address (48 bits), sequence number and last clock
Version number stamped into the UUID to identify it as time-based.
Returns the UUID generator used by generate. Useful if you need to mess with it, e.g. force next sequence when forking (e.g. Unicorn, Resque):
after_fork do
UUID.generator.next_sequence
end
# File lib/uuid.rb, line 127 def self.generator @uuid ||= new end
The access mode of the state file. Set it with state_file.
# File lib/uuid.rb, line 103 def self.mode @mode end
# File lib/uuid.rb, line 107 def self.mode=(mode) @mode = mode end
Create a new UUID generator. You really only need to do this once.
# File lib/uuid.rb, line 245 def initialize @drift = 0 @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i @mutex = Mutex.new state_file = self.class.state_file if state_file && File.size?(state_file) then next_sequence else @mac = mac_address fail "Cannot determine MAC address from any available interface, tried with #{mac_address}" if @mac == 0 @sequence = rand 0x10000 # Ensure the mode is respected, even with a restrictive umask File.open(state_file, 'w') { |f| f.chmod(self.class.mode) } if state_file && !File.exists?(state_file) if state_file open_lock 'wb' do |io| write_state io end end end end
Creates an empty state file in /var/tmp/ruby-uuid or the windows common application data directory using mode 0644. Call with a different mode before creating a UUID generator if you want to open access beyond your user by default.
If the default state dir is not writable, UUID falls back to ~/.ruby-uuid.
State files are not portable across machines.
# File lib/uuid.rb, line 147 def self.state_file(mode = 0644) return @state_file unless @state_file.nil? @mode = mode begin require 'Win32API' csidl_common_appdata = 0x0023 path = 0.chr * 260 get_folder_path = Win32API.new('shell32', 'SHGetFolderPath', 'LLLLP', 'L') get_folder_path.call 0, csidl_common_appdata, 0, 1, path state_dir = File.join(path.strip) rescue LoadError state_dir = File.join('', 'var', 'tmp') end @state_file = File.join(state_dir, 'ruby-uuid') if !File.writable?(state_dir) || (File.exists?(@state_file) && !File.writable?(@state_file)) then @state_file = File.expand_path('.ruby-uuid', '~') end @state_file end
Specify the path of the state file. Use this if you need a different location for your state file.
Set to false if your system cannot use a state file (e.g. many shared hosts).
# File lib/uuid.rb, line 180 def self.state_file=(path) @state_file = path @mode ||= 0644 end
Returns true if uuid
is in compact, default or urn formats.
Does not validate the layout (RFC 4122 section 4) of the UUID.
# File lib/uuid.rb, line 188 def self.validate(uuid) return true if uuid =~ /\A[\da-f]{32}\z/ return true if uuid =~ /\A(urn:uuid:)?[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}\z/ end
Generates a new UUID string using
format
. See FORMATS for a
list of supported formats.
# File lib/uuid.rb, line 272 def generate(format = :default) template = FORMATS[format] raise ArgumentError, "invalid UUID format #{format.inspect}" unless template # The clock must be monotonically increasing. The clock resolution is at # best 100 ns (UUID spec), but practically may be lower (on my setup, # around 1ms). If this method is called too fast, we don't have a # monotonically increasing clock, so the solution is to just wait. # # It is possible for the clock to be adjusted backwards, in which case we # would end up blocking for a long time. When backward clock is detected, # we prevent duplicates by asking for a new sequence number and continue # with the new clock. clock = @mutex.synchronize do clock = (Time.new.to_f * CLOCK_MULTIPLIER).to_i & 0xFFFFFFFFFFFFFFF0 if clock > @last_clock then @drift = 0 @last_clock = clock elsif clock == @last_clock then drift = @drift += 1 if drift < 10000 then @last_clock += 1 else Thread.pass nil end else next_sequence @last_clock = clock end end until clock template % [ clock & 0xFFFFFFFF, (clock >> 32) & 0xFFFF, ((clock >> 48) & 0xFFFF | VERSION_CLOCK), @sequence & 0xFFFF, @mac & 0xFFFFFFFFFFFF ] end
Uses system calls to get a mac address
# File lib/uuid.rb, line 227 def iee_mac_address begin Mac.addr.gsub(/:|-/, '').hex & 0x7FFFFFFFFFFF rescue 0 end end
# File lib/uuid.rb, line 343 def inspect mac = ("%012x" % @mac).scan(/[0-9a-f]{2}/).join(':') "MAC: #{mac} Sequence: #{@sequence}" end
return #iee_mac_address if available, #pseudo_mac_address otherwise
# File lib/uuid.rb, line 238 def mac_address return iee_mac_address unless iee_mac_address == 0 return pseudo_mac_address end
Updates the state file with a new sequence number.
# File lib/uuid.rb, line 319 def next_sequence if self.class.state_file open_lock 'rb+' do |io| @mac, @sequence, @last_clock = read_state(io) io.rewind io.truncate 0 @sequence += 1 write_state io end else @sequence += 1 end rescue Errno::ENOENT open_lock 'w' do |io| write_state io end ensure @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i @drift = 0 end
Generate a pseudo MAC address because we have no pure-ruby way to know the MAC address of the NIC this system uses. Note that cheating with pseudo arresses here is completely legal: see Section 4.5 of RFC4122 for details.
This implementation is shamelessly stolen from
https://github.com/spectra/ruby-uuid/blob/master/uuid.rb
Thanks spectra.
# File lib/uuid.rb, line 204 def pseudo_mac_address sha1 = ::Digest::SHA1.new 256.times do r = [rand(0x100000000)].pack "N" sha1.update r end str = sha1.digest r = rand 14 # 20-6 node = str[r, 6] || str if RUBY_VERSION >= "1.9.0" nnode = node.bytes.to_a nnode[0] |= 0x01 node = '' nnode.each { |s| node << s.chr } else node[0] |= 0x01 # multicast bit end node.bytes.collect{|b|b.to_s(16)}.join.hex & 0x7FFFFFFFFFFF end
Open the state file with an exclusive lock and access mode
mode
.
# File lib/uuid.rb, line 352 def open_lock(mode) File.open self.class.state_file, mode, self.class.mode do |io| begin io.flock File::LOCK_EX yield io ensure io.flock File::LOCK_UN end end end
Read the state from io
# File lib/uuid.rb, line 365 def read_state(io) mac1, mac2, seq, last_clock = io.read(32).unpack(STATE_FILE_FORMAT) mac = (mac1 << 32) + mac2 return mac, seq, last_clock end
Write that state to io
# File lib/uuid.rb, line 375 def write_state(io) mac2 = @mac & 0xffffffff mac1 = (@mac >> 32) & 0xffff io.write [mac1, mac2, @sequence, @last_clock].pack(STATE_FILE_FORMAT) end