Class: UUID
- Inherits:
-
Object
- Object
- UUID
- Defined in:
- lib/uuid.rb
Overview
Generating UUIDs
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
UUIDs in Brief
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.
Defined Under Namespace
Modules: Version
Constant Summary collapse
- VERSION =
Version::STRING
- CLOCK_MULTIPLIER =
Clock multiplier. Converts Time (resolution: seconds) to UUID clock (resolution: 10ns)
10000000
- CLOCK_GAPS =
Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time ticks.
100000
- VERSION_CLOCK =
Version number stamped into the UUID to identify it as time-based.
0x0100
- FORMATS =
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 :teenie
-
converts numeric portions of default format to base62
{ :compact => '%08x%04x%04x%04x%012x', :default => '%08x-%04x-%04x-%04x-%012x', :urn => 'urn:uuid:%08x-%04x-%04x-%04x-%012x', :teenie => '%6.6s%3.3s%3.3s%3.3s%9.9s', }
- STATE_FILE_FORMAT =
MAC address (48 bits), sequence number and last clock
'SLLQ'
Class Method Summary collapse
-
.generate(format = :default) ⇒ Object
Generates a new UUID string using
format
. -
.mode ⇒ Object
The access mode of the state file.
-
.state_file(mode = 0644) ⇒ Object
Creates an empty state file in /var/tmp/ruby-uuid or the windows common application data directory using mode 0644.
-
.state_file=(path) ⇒ Object
Specify the path of the state file.
-
.translate(uuid, source_format, target_format) ⇒ Object
translate one format uuid to another.
-
.validate(uuid) ⇒ Object
Returns true if
uuid
is in compact, default or urn formats. -
.validate_teenie(uuid) ⇒ Object
Returns true if
uuid
is in teenie format.
Instance Method Summary collapse
-
#generate(format = :default) ⇒ Object
Generates a new UUID string using
format
. -
#generate_from_parts(format, part1, part2, part3, part4, part5) ⇒ Object
this is used both for generation and translation.
-
#initialize ⇒ UUID
constructor
Create a new UUID generator.
- #inspect ⇒ Object
-
#next_sequence ⇒ Object
Updates the state file with a new sequence number.
Constructor Details
#initialize ⇒ UUID
Create a new UUID generator. You really only need to do this once.
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/uuid.rb', line 222 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.addr.gsub(/:|-/, '').hex & 0x7FFFFFFFFFFF fail "Cannot determine MAC address from any available interface, tried with #{Mac.addr}" if @mac == 0 @sequence = rand 0x10000 if state_file open_lock 'w' do |io| write_state io end end end end |
Class Method Details
.generate(format = :default) ⇒ Object
Generates a new UUID string using format
. See FORMATS for a list of supported formats.
123 124 125 126 |
# File 'lib/uuid.rb', line 123 def self.generate(format = :default) @uuid ||= new @uuid.generate format end |
.mode ⇒ Object
The access mode of the state file. Set it with state_file.
115 116 117 |
# File 'lib/uuid.rb', line 115 def self.mode @mode end |
.state_file(mode = 0644) ⇒ Object
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.
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/uuid.rb', line 137 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 if File.writable?(state_dir) then @state_file = File.join(state_dir, 'ruby-uuid') else @state_file = File.(File.join('~', '.ruby-uuid')) end @state_file end |
.state_file=(path) ⇒ Object
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).
170 171 172 |
# File 'lib/uuid.rb', line 170 def self.state_file=(path) @state_file = path end |
.translate(uuid, source_format, target_format) ⇒ Object
translate one format uuid to another
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/uuid.rb', line 192 def self.translate(uuid, source_format, target_format) if source_format == :teenie raise "invalid uuid passed in for translation" unless UUID.validate_teenie(uuid) else raise "invalid uuid passed in for translation" unless UUID.validate(uuid) end raise "invalid source format passed in for tranlation" unless FORMATS[source_format] raise "invalid target format passed in for tranlation" unless FORMATS[target_format] raise "it's a waste of time to translate one format to another" unless source_format != target_format parts = Array.new if source_format != :teenie parts = uuid.scanf(FORMATS[source_format]) else # this has to stay in synch with the :teenie format parts[0] = uuid[0..5].base62_decode parts[1] = uuid[6..8].base62_decode parts[2] = uuid[9..11].base62_decode parts[3] = uuid[12..14].base62_decode parts[4] = uuid[15..23].base62_decode end @uuid ||= new @uuid.generate_from_parts(target_format, parts[0], parts[1], parts[2], parts[3], parts[4]) end |
.validate(uuid) ⇒ Object
Returns true if uuid
is in compact, default or urn formats. Does not validate the layout (RFC 4122 section 4) of the UUID. does not validate :teenie format. see validate_teenie
178 179 180 181 182 |
# File 'lib/uuid.rb', line 178 def self.validate(uuid) return true if uuid =~ /\A[\da-f]{32}\z/i return true if uuid =~ /\A(urn:uuid:)?[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}\z/i end |
.validate_teenie(uuid) ⇒ Object
Returns true if uuid
is in teenie format
186 187 188 |
# File 'lib/uuid.rb', line 186 def self.validate_teenie(uuid) return true if uuid =~ /\A[\da-fA-f]{24}\z/i end |
Instance Method Details
#generate(format = :default) ⇒ Object
Generates a new UUID string using format
. See FORMATS for a list of supported formats.
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/uuid.rb', line 246 def generate(format = :default) # 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 part1 = clock & 0xFFFFFFFF part2 = (clock >> 32) & 0xFFFF part3 = ((clock >> 48) & 0xFFFF | VERSION_CLOCK) part4 = @sequence & 0xFFFF part5 = @mac & 0xFFFFFFFFFFFF generate_from_parts(format, part1, part2, part3, part4, part5) end |
#generate_from_parts(format, part1, part2, part3, part4, part5) ⇒ Object
this is used both for generation and translation
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/uuid.rb', line 321 def generate_from_parts(format, part1, part2, part3, part4, part5) template = FORMATS[format] raise ArgumentError, "invalid UUID format #{format.inspect}" unless template # for this special case, the parts are going to be strings which we will 0 pad if format == :teenie part1 = part1.base62_encode part2 = part2.base62_encode part3 = part3.base62_encode part4 = part4.base62_encode part5 = part5.base62_encode (template % [part1, part2, part3, part4, part5]).gsub(' ', '0') else template % [part1, part2, part3, part4, part5] end end |
#inspect ⇒ Object
314 315 316 317 |
# File 'lib/uuid.rb', line 314 def inspect mac = ("%012x" % @mac).scan(/[0-9a-f]{2}/).join(':') "MAC: #{mac} Sequence: #{@sequence}" end |
#next_sequence ⇒ Object
Updates the state file with a new sequence number.
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/uuid.rb', line 290 def next_sequence if self.class.state_file open_lock 'r+' 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 |