Module: MakeId
- Defined in:
- lib/make_id.rb,
lib/make_id/version.rb
Overview
MakeID generates record Identifiers other than sequential integers. MakeId - From the “make_id” gem found at github.com/afair/make_id License - MIT, see the LICENSE file in the gem’s source code. Adopt - Copy this file to your application with the above attribution to
allow others to find fixes, documentation, and new features.
Defined Under Namespace
Classes: Error
Constant Summary collapse
- BASE32 =
Base32 avoids ambiguous letters for 0/o/O and i/I/l/1. This is useful for human-interpreted codes for serial numbers, license keys, etc.
"0123456789ABCDEFGHJKMNPQRSTVWXYZ"- BASE62 =
Ruby’s Integer.to_s(2..36) uses extended Hexadecimal: 0-9,a-z. Base62 includes upper-case letters as well, maintaining ASCII cardinality.
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"- BASE64 =
Base64 Does not use ASCII-collating (sort) character set
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"- BASE94 =
Base94 extends Base64 with all printable ASCII special characters. Using Base of 90 won’t use quotes, backslash
BASE64 + %q(!$%&()*,-.:;<=>?@[]^_{|}~#`'"\\)
- URL_BASE64 =
URL-Encoded Base64 swaps the + and / for - and _ respectively to avoid URL Encoding
BASE64.tr("+/", "-_")
- EPOCH_TWITTER =
TWitter Snowflake starts its epoch at this time.
Time.utc(2006, 3, 21, 20, 50, 14)
- VERSION =
"0.1.3"- @@app_worker_id =
ENV.fetch("APP_WORKER_ID", 0)
- @@epoch =
Time.utc(2020)
- @@counter_time =
0- @@counter =
0- @@check_proc =
nil
Class Method Summary collapse
-
.app_worker_id ⇒ Object
Returns the current worker id.
-
.app_worker_id=(id) ⇒ Object
Set your default snowflake default id.
-
.append_check_digit(id, base = 10) ⇒ Object
Adds a check digit to the end of an id string.
- .application_epoch ⇒ Object
-
.base_characters(base, chars = nil, shuffle_seed: nil) ⇒ Object
Returns the refined base and characters used for the base conversions.
-
.base_to_int(string, base = 62, check_digit: false) ⇒ Object
Parses a string as a base n number and returns its decimal integer value.
-
.check_proc=(proc) ⇒ Object
Set a custom check digit proc that takes the id string and base as argumentsA and returns a character to append to the end of the id.
-
.code(size = 8, group: 0, delimiter: "-") ⇒ Object
Returns a new, ramdonly-generated Base-32 code (no ambiguous characters).
-
.combine_snowflake_parts(milliseconds, worker_id, sequence) ⇒ Object
Creates the final snowflake by bit-mapping the constituent parts into the whole.
-
.compute_check_digit(id, base = 10) ⇒ Object
Returns a character computed using the CRC32 algorithm Uses a pre-defined check_proc if configured.
- .decode_alphabet(string, alpha = BASE32, seed: nil, base: nil) ⇒ Object
- .encode_alphabet(int, alpha = BASE62, seed: nil) ⇒ Object
- .epoch ⇒ Object
-
.epoch=(arg) ⇒ Object
Sets the start year for snowflake epoch.
-
.event_id(size: 12, check_digit: false, time: nil) ⇒ Object
Event Id - A nano_id, but timestamped event identifier: YMDHMSUUrrrrc.
-
.id(bytes: 8, base: 10, absolute: true, check_digit: false) ⇒ Object
Random Integer ID.
- .id_password(bytes: 8, base: 10, absolute: true, alpha: nil) ⇒ Object
-
.int_to_base(int, base = 62, check_digit: false, chars: nil) ⇒ Object
Takes an integer and a base (from 2 to 62) and converts the number.
-
.nano_id(bytes: 8, base: 62, check_digit: true) ⇒ Object
Generates a 8-byte “nano id”, a string of random characters of the given alphabet, suitable for URL’s or where you don’t want to show a sequential number.
- .next_millisecond_sequence(milliseconds) ⇒ Object
-
.pack_int_parts(*pairs) ⇒ Object
Build an integer value from pairs of [bits, value].
-
.remove_check_digit(id, base = 10) ⇒ Object
Removes and validates the check digit.
-
.request_id(time: nil, sequence_method: :counter) ⇒ Object
Returns a 16-character request id string in Base32 of format: YMDHsssuuqqwwrrr Use substring [3, 8] (Hsssuuqq) for a short 8-character version, easier for human scanning.
-
.snowflake_datetime_uuid(time: nil, format: true, worker_id: nil, utc: true) ⇒ Object
Returns UUID with columnar date parts: yyyymmdd-hhmm-ssuu-uwww-rrrrrrrrrrrr.
-
.snowflake_id(worker_id: nil, base: 10, sequence_method: :counter) ⇒ Object
Returns an 8-byte integer snowflake id that can be reverse parsed.
-
.snowflake_uuid(time: nil, format: true, worker_id: nil, application_epoch: false) ⇒ Object
Returns uuid with Unix epoch time sort in format: ssssssss-uuuw-wwrr-rrrr-rrrrrrrrrrrr Specify ‘application_epoch: true` to use instead of Unix epoch.
-
.string_id(string, random_bits: 0, base: 10, bytes: 8) ⇒ Object
Takes a string and returns an 8-byte integer for the string.
-
.time_id(base: 10, precision: 4, random_bits: 18, time: nil, chars: nil) ⇒ Object
Returns an id using the current epoch time with floating precision and random bits.
-
.time_token(size: 0, base: 62, chars: nil, time: nil, precision: 3, random_bits: 8) ⇒ Object
Returns a string from the time with floating precision, and a random number appended.
-
.token(size = 16, base: 62, chars: nil) ⇒ Object
Returns a random alphanumeric string of the given base, default of 62.
-
.uuid ⇒ Object
Returns a 16-byte securely-random generated UUID v4.
-
.uuid_to_base(uuid, base = 10) ⇒ Object
Accepts a hext UUID string and returns the integer value in the given base.
-
.valid_check_digit?(id, base = 10) ⇒ Boolean
Takes an id with a check digit and return true if the check digit matches.
-
.verify_code(nanoid, check_digit: false) ⇒ Object
Given a nano_id, replaces visually ambiguous characters and verifies the check digit.
Class Method Details
.app_worker_id ⇒ Object
Returns the current worker id
53 54 55 |
# File 'lib/make_id.rb', line 53 def self.app_worker_id @@app_worker_id end |
.app_worker_id=(id) ⇒ Object
Set your default snowflake default id. This is a 10-bit number (0..1023) that designates your: datacenter, machine, and/or process that generated it. This can be overridden by setting the environment variable APP_WORKER_ID or by the caller. Usage (configuration): MakeId.app_worker_id = 123
48 49 50 |
# File 'lib/make_id.rb', line 48 def self.app_worker_id=(id) @@app_worker_id = id.to_i & 0x3ff end |
.append_check_digit(id, base = 10) ⇒ Object
Adds a check digit to the end of an id string. This check digit is derived from the CRC-32 (Cyclical Redundancy Check) value of the id string
415 416 417 |
# File 'lib/make_id.rb', line 415 def self.append_check_digit(id, base = 10) id.to_s + compute_check_digit(id, base) end |
.application_epoch ⇒ Object
72 73 74 |
# File 'lib/make_id.rb', line 72 def self.application_epoch Time.now.to_i - @@epoch.to_i end |
.base_characters(base, chars = nil, shuffle_seed: nil) ⇒ Object
Returns the refined base and characters used for the base conversions
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
# File 'lib/make_id.rb', line 388 def self.base_characters(base, chars = nil, shuffle_seed: nil) if chars base ||= chars.size chars = chars[0..(base - 1)] elsif base > 94 || base < 2 raise Error.new("Base#{base} is not supported") elsif base > 64 chars = BASE94[0..(base - 1)] elsif base > 62 chars = URL_BASE64[0..(base - 1)] elsif base == 32 chars = BASE32 else chars = BASE62[0..(base - 1)] end chars = chars.chars.shuffle(random: Random.new(shuffle_seed)).join if shuffle_seed base = chars.size [base, chars] end |
.base_to_int(string, base = 62, check_digit: false) ⇒ Object
Parses a string as a base n number and returns its decimal integer value
358 359 360 361 362 |
# File 'lib/make_id.rb', line 358 def self.base_to_int(string, base = 62, check_digit: false) # TODO: check_digit _, chars = base_characters(base, chars) decode_alphabet(string, chars) end |
.check_proc=(proc) ⇒ Object
Set a custom check digit proc that takes the id string and base as argumentsA and returns a character to append to the end of the id.
59 60 61 |
# File 'lib/make_id.rb', line 59 def self.check_proc=(proc) @@check_proc = proc end |
.code(size = 8, group: 0, delimiter: "-") ⇒ Object
Returns a new, ramdonly-generated Base-32 code (no ambiguous characters). Use this for Two-Factor Authorization, and serial number codes to be input by users. Use verify_code() to “fix” user-input of codes.
151 152 153 154 155 |
# File 'lib/make_id.rb', line 151 def self.code(size = 8, group: 0, delimiter: "-") id = token(size, base: 32) id = id.chars.each_slice(group).map(&:join).join(delimiter) if group > 0 id end |
.combine_snowflake_parts(milliseconds, worker_id, sequence) ⇒ Object
Creates the final snowflake by bit-mapping the constituent parts into the whole
306 307 308 309 310 311 312 313 314 |
# File 'lib/make_id.rb', line 306 def self.combine_snowflake_parts(milliseconds, worker_id, sequence) id = milliseconds & 0x1ffffffffff # 0 (sign) + lower 41bits id <<= 10 id |= worker_id & 0x3ff # 10bits (0..1023) id <<= 12 id |= (sequence & 0xfff) # 12 bits (0..4095) id end |
.compute_check_digit(id, base = 10) ⇒ Object
Returns a character computed using the CRC32 algorithm Uses a pre-defined check_proc if configured. See check_proc=().
427 428 429 430 431 |
# File 'lib/make_id.rb', line 427 def self.compute_check_digit(id, base = 10) return @@check_proc.call(id, base) if @@check_proc.is_a?(Proc) int_to_base(Zlib.crc32(id.to_s) % base, base) end |
.decode_alphabet(string, alpha = BASE32, seed: nil, base: nil) ⇒ Object
377 378 379 380 381 382 383 384 385 |
# File 'lib/make_id.rb', line 377 def self.decode_alphabet(string, alpha = BASE32, seed: nil, base: nil) base ||= alpha.size alpha = alpha.chars.shuffle(random: Random.new(seed)).join if seed int = 0 string.each_char { |c| int = int * base + alpha.index(c) } int rescue nil end |
.encode_alphabet(int, alpha = BASE62, seed: nil) ⇒ Object
366 367 368 369 370 371 372 373 374 375 |
# File 'lib/make_id.rb', line 366 def self.encode_alphabet(int, alpha = BASE62, seed: nil) base = alpha.size alpha = alpha.chars.shuffle(random: Random.new(seed)).join if seed id = "" while int > (base - 1) id = alpha[int % base] + id int /= base end alpha[int] + id end |
.epoch ⇒ Object
68 69 70 |
# File 'lib/make_id.rb', line 68 def self.epoch @@epoch end |
.epoch=(arg) ⇒ Object
Sets the start year for snowflake epoch
64 65 66 |
# File 'lib/make_id.rb', line 64 def self.epoch=(arg) @@epoch = arg.is_a?(Time) ? arg : Time.utc(arg) end |
.event_id(size: 12, check_digit: false, time: nil) ⇒ Object
Event Id - A nano_id, but timestamped event identifier: YMDHMSUUrrrrc
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/make_id.rb', line 173 def self.event_id(size: 12, check_digit: false, time: nil) time ||= Time.new.utc usec = int_to_base((time.subsec.to_f * 62 * 62).to_i, 62) parts = [ BASE62[time.year % @@epoch.year], BASE62[time.month], BASE62[time.day], BASE62[time.hour], BASE62[time.min], BASE62[time.sec], usec.rjust(2, "0") # 2-chars, 0..3843 ] nano_size = size - 8 - (check_digit ? 1 : 0) parts << token(nano_size, base: 62) if nano_size > 0 id = check_digit ? append_check_digit(parts.join, 62) : parts.join id[0, size] end |
.id(bytes: 8, base: 10, absolute: true, check_digit: false) ⇒ Object
Random Integer ID
81 82 83 84 85 86 87 |
# File 'lib/make_id.rb', line 81 def self.id(bytes: 8, base: 10, absolute: true, check_digit: false) id = SecureRandom.random_number(2**(bytes * 8) - 2) + 1 # +1 to avoid zero id = id.abs if absolute id = int_to_base(id, base) unless base == 10 id = append_check_digit(id, base) if check_digit id end |
.id_password(bytes: 8, base: 10, absolute: true, alpha: nil) ⇒ Object
89 90 91 92 93 |
# File 'lib/make_id.rb', line 89 def self.id_password(bytes: 8, base: 10, absolute: true, alpha: nil) id = id(bytes: bytes) pass = id(bytes: 16) [int_to_base(id, base), encode_alphabet(pass, alpha || BASE94, seed: id)] end |
.int_to_base(int, base = 62, check_digit: false, chars: nil) ⇒ Object
Takes an integer and a base (from 2 to 62) and converts the number. Ruby’s int.to_s(base) only goes to 36. Base 32 is special as it does not contain visually ambiguous characters (1, not i, I, l, L) and (0, not o or O) Which is useful for serial numbers or codes the user has to read or type
349 350 351 352 353 |
# File 'lib/make_id.rb', line 349 def self.int_to_base(int, base = 62, check_digit: false, chars: nil) base, chars = base_characters(base, chars) id = encode_alphabet(int, chars) check_digit ? append_check_digit(id, base) : id end |
.nano_id(bytes: 8, base: 62, check_digit: true) ⇒ Object
Generates a 8-byte “nano id”, a string of random characters of the given alphabet, suitable for URL’s or where you don’t want to show a sequential number. A check digit can be added to the end to help prevent typos.
98 99 100 101 102 |
# File 'lib/make_id.rb', line 98 def self.nano_id(bytes: 8, base: 62, check_digit: true) bytes -= 1 if check_digit id = id(bytes: bytes, base: base) check_digit ? append_check_digit(id, base) : id end |
.next_millisecond_sequence(milliseconds) ⇒ Object
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
# File 'lib/make_id.rb', line 316 def self.next_millisecond_sequence(milliseconds) sequence = 0 semaphore = Mutex.new semaphore.synchronize do if @@counter_time != milliseconds @@counter_time = milliseconds @@counter = 0 end sequence = @@counter % 4095 @@counter += 1 end sequence end |
.pack_int_parts(*pairs) ⇒ Object
Build an integer value from pairs of [bits, value]
333 334 335 336 337 338 339 |
# File 'lib/make_id.rb', line 333 def self.pack_int_parts(*pairs) int = 0 pairs.each do |bits, value| int = (int << bits) | (value & ((1 << bits) - 1)) end int end |
.remove_check_digit(id, base = 10) ⇒ Object
Removes and validates the check digit. Retuns nil if the check digit is invalid.
420 421 422 423 |
# File 'lib/make_id.rb', line 420 def self.remove_check_digit(id, base = 10) id, cd = id.to_s[0..-2], id.to_s[-1] valid_check_digit?(id + cd, base) ? id : nil end |
.request_id(time: nil, sequence_method: :counter) ⇒ Object
Returns a 16-character request id string in Base32 of format: YMDHsssuuqqwwrrr Use substring [3, 8] (Hsssuuqq) for a short 8-character version, easier for human scanning.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/make_id.rb', line 193 def self.request_id(time: nil, sequence_method: :counter) time ||= Time.new seconds = time.to_i - Time.new(time.year, time.month, time.day, time.hour).to_i # time.utc.hour?? sequence = if sequence_method == :counter next_millisecond_sequence(((Time.now.utc.to_f - @@epoch.to_i) * 1000).to_i) elsif sequence_method == :random SecureRandom.random_number(4095) end [ BASE62[time.year % @@epoch.year], BASE62[time.month], BASE62[time.day], # "-", BASE62[time.hour].downcase, int_to_base(seconds, 32).rjust(3, "0"), # 3 chars int_to_base((time.subsec.to_f * 32 * 32).to_i, 32), # 2 chars sequence.to_s(32).rjust(2, "0"), # 2 chars "-", (app_worker_id % 1024).to_s(32).rjust(2, "0"), # 2 chars token(3, base: 32) ].join end |
.snowflake_datetime_uuid(time: nil, format: true, worker_id: nil, utc: true) ⇒ Object
Returns UUID with columnar date parts: yyyymmdd-hhmm-ssuu-uwww-rrrrrrrrrrrr
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/make_id.rb', line 287 def self.snowflake_datetime_uuid(time: nil, format: true, worker_id: nil, utc: true) time ||= Time.new time = time.utc if utc worker_id ||= app_worker_id id = [ time.year, time.month.to_s.rjust(2, "0"), time.day.to_s.rjust(2, "0"), time.hour.to_s.rjust(2, "0"), time.min.to_s.rjust(2, "0"), time.sec.to_s.rjust(2, "0"), (time.subsec.to_f * 1000).to_i.to_s(16).rjust(3, "0"), (worker_id % 1024).to_s(16).rjust(3, "0"), SecureRandom.hex(6) ].join format ? "#{id[0..7]}-#{id[8..11]}-#{id[12..15]}-#{id[16..19]}-#{id[20..31]}" : id end |
.snowflake_id(worker_id: nil, base: 10, sequence_method: :counter) ⇒ Object
Returns an 8-byte integer snowflake id that can be reverse parsed. sequence_counter can be :counter for a rotating integer, or :random
255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/make_id.rb', line 255 def self.snowflake_id(worker_id: nil, base: 10, sequence_method: :counter) milliseconds = ((Time.now.utc.to_f - @@epoch.to_i) * 1000).to_i worker_id ||= app_worker_id sequence = 0 if sequence_method == :counter sequence = next_millisecond_sequence(milliseconds) elsif sequence_method == :random sequence = SecureRandom.random_number(4095) end id = combine_snowflake_parts(milliseconds, worker_id, sequence) (base == 10) ? id : int_to_base(id, base) end |
.snowflake_uuid(time: nil, format: true, worker_id: nil, application_epoch: false) ⇒ Object
Returns uuid with Unix epoch time sort in format: ssssssss-uuuw-wwrr-rrrr-rrrrrrrrrrrr Specify ‘application_epoch: true` to use instead of Unix epoch
271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/make_id.rb', line 271 def self.snowflake_uuid(time: nil, format: true, worker_id: nil, application_epoch: false) time ||= Time.new seconds = time.to_i seconds -= @@epoch.to_i if application_epoch worker_id ||= app_worker_id parts = [ seconds.to_s(16).rjust(8, "0"), (time.subsec.to_f * 1000).to_i.to_s(16).rjust(3, "0"), (worker_id % 1024).to_s(16).rjust(3, "0"), SecureRandom.hex(9) ] id = append_check_digit(parts.join, 16).downcase format ? "#{id[0..7]}-#{id[8..11]}-#{id[12..15]}-#{id[16..19]}-#{id[20..31]}" : id end |
.string_id(string, random_bits: 0, base: 10, bytes: 8) ⇒ Object
Takes a string and returns an 8-byte integer for the string. This implementation uses the SHA256 hash of the string to generate the ID. You can also have random bits added to the ID to make it unique. This is not a unique ID, but a repeatable ID for the same string (unless random_bits is used), and can be used like a hashing value.
109 110 111 112 113 114 115 116 117 |
# File 'lib/make_id.rb', line 109 def self.string_id(string, random_bits: 0, base: 10, bytes: 8) id = Digest::SHA256.hexdigest(string)[0, bytes * 2].to_i(16) if random_bits > 0 id >> random_bits id << random_bits id |= SecureRandom.random_number(2**random_bits - 1) end (base == 10) ? id : int_to_base(id, base) end |
.time_id(base: 10, precision: 4, random_bits: 18, time: nil, chars: nil) ⇒ Object
Returns an id using the current epoch time with floating precision and random bits. Be default, this returns an 8-byte integer in ascending order within precision. Format is approximately “SSSSSSSSSMMMRRR” (seconds, milliseconds, random).
-
precision (1..5) The number of milliseconds decimeal places to use. Default is 4.
-
random_bits (1..) The number of bits to use for the random number. Default is 18.
-
time - Time object to use for the token. Default is Time.now
-
base - The base to use for the id. Default is 10
-
chars - The character set to use for the base conversion. Default is BASE62
224 225 226 227 228 |
# File 'lib/make_id.rb', line 224 def self.time_id(base: 10, precision: 4, random_bits: 18, time: nil, chars: nil) seconds = ((time || Time.now).to_f * (10**precision)).to_i id = (seconds * (2**random_bits)) | SecureRandom.random_number(2**random_bits - 1) (base == 10) ? id : int_to_base(id, base, chars: chars) end |
.time_token(size: 0, base: 62, chars: nil, time: nil, precision: 3, random_bits: 8) ⇒ Object
Returns a string from the time with floating precision, and a random number appended. This calls time_id() and returns a token of the given size. If size is zero, the full token is returned. If size is greater than the token, the token is right-justified with zeros. If the token is larger than size, the right-most characters are returned.
235 236 237 238 239 240 241 242 243 244 |
# File 'lib/make_id.rb', line 235 def self.time_token(size: 0, base: 62, chars: nil, time: nil, precision: 3, random_bits: 8) token = time_id(base: base, precision: precision, random_bits: random_bits, time: time, chars: chars) if size == 0 token elsif token.size < size token.rjust(size, "0") else token[-size, size] end end |
.token(size = 16, base: 62, chars: nil) ⇒ Object
Returns a random alphanumeric string of the given base, default of 62. Base 64 uses URL-safe characters. Bases 19-32 and below use a special character set that avoids visually ambiguous characters. Other bases utilize the full alphanumeric characer set (digits, lower/upper letters).
143 144 145 146 |
# File 'lib/make_id.rb', line 143 def self.token(size = 16, base: 62, chars: nil) _, chars = base_characters(base, chars) SecureRandom.alphanumeric(size, chars: chars.chars) end |
.uuid ⇒ Object
Returns a 16-byte securely-random generated UUID v4
124 125 126 |
# File 'lib/make_id.rb', line 124 def self.uuid SecureRandom.uuid end |
.uuid_to_base(uuid, base = 10) ⇒ Object
Accepts a hext UUID string and returns the integer value in the given base. If base is specified, it will convert to that base using MakeId utilities.
130 131 132 133 |
# File 'lib/make_id.rb', line 130 def self.uuid_to_base(uuid, base = 10) int = uuid.delete("-").to_i(16) (base == 10) ? int : int_to_base(int, base) end |
.valid_check_digit?(id, base = 10) ⇒ Boolean
Takes an id with a check digit and return true if the check digit matches
434 435 436 |
# File 'lib/make_id.rb', line 434 def self.valid_check_digit?(id, base = 10) id == append_check_digit(id[0..-2], base) end |
.verify_code(nanoid, check_digit: false) ⇒ Object
Given a nano_id, replaces visually ambiguous characters and verifies the check digit. Returns the corrected id or nil if the check digit is invalid.
159 160 161 162 163 164 165 166 |
# File 'lib/make_id.rb', line 159 def self.verify_code(nanoid, check_digit: false) nanoid = nanoid.gsub(/\W/, "") nanoid = nanoid.gsub(/[oO]/, "0") nanoid = nanoid.gsub(/[lLiI]/, "1") nanoid = nanoid.upcase return valid_check_digit?(nanoid, base: 32) if check_digit nanoid end |