cuid 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rbenv-version +1 -0
- data/.travis.yml +6 -0
- data/.yardopts +5 -0
- data/Gemfile +5 -0
- data/README.md +71 -0
- data/Rakefile +8 -0
- data/cuid.gemspec +17 -0
- data/doc/index.html +1164 -0
- data/lib/cuid.rb +175 -0
- data/lib/cuid/version.rb +3 -0
- data/test/test_cuid.rb +58 -0
- metadata +58 -0
data/lib/cuid.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
require "socket"
|
2
|
+
require 'cuid/version'
|
3
|
+
begin
|
4
|
+
require "securerandom"
|
5
|
+
rescue LoadError
|
6
|
+
end
|
7
|
+
|
8
|
+
##
|
9
|
+
# Cuid is a library for generating unique collision-resistant IDs optimized for horizontal scaling and performance
|
10
|
+
#
|
11
|
+
# @example Generate a hash
|
12
|
+
# hash = Cuid::generate #=> "ch8qypsnz0000a4welw8anyr"
|
13
|
+
#
|
14
|
+
# @example Generate 2 hashes
|
15
|
+
# hashes = Cuid::generate(2) #=> ["ch8qyq35f0002a4wekjcwmh30", "ch8qyq35f0003a4weewy22izq"]
|
16
|
+
#
|
17
|
+
# @see http://github.com/dilvie/cuid Original author's detailed explanation of the cuid spec
|
18
|
+
|
19
|
+
module Cuid
|
20
|
+
##
|
21
|
+
# @private
|
22
|
+
@count = 0
|
23
|
+
|
24
|
+
##
|
25
|
+
# length of each segment of the hash
|
26
|
+
BLOCK_SIZE = 4
|
27
|
+
|
28
|
+
##
|
29
|
+
# size of the alphabet (e.g. base36 is [a-z0-9])
|
30
|
+
BASE = 36
|
31
|
+
|
32
|
+
##
|
33
|
+
# @private
|
34
|
+
# maximum number that can be stored in a block of the specified size using the specified alphabet
|
35
|
+
DISCRETE_VALUES = (BASE ** BLOCK_SIZE) - 1
|
36
|
+
|
37
|
+
##
|
38
|
+
# size of the random segment of the block
|
39
|
+
RAND_SIZE = BLOCK_SIZE * 2
|
40
|
+
|
41
|
+
##
|
42
|
+
# @private
|
43
|
+
# maximum number that can be stored in the random block
|
44
|
+
RAND_MAX = (BASE ** RAND_SIZE) - 1
|
45
|
+
|
46
|
+
##
|
47
|
+
# @private
|
48
|
+
# minimum number that can be stored in the random block (otherwise it will be too short)
|
49
|
+
RAND_MIN = BASE ** (RAND_SIZE - 1)
|
50
|
+
|
51
|
+
##
|
52
|
+
# @private
|
53
|
+
# first letter of the hash
|
54
|
+
LETTER = "c"
|
55
|
+
|
56
|
+
class << self
|
57
|
+
|
58
|
+
##
|
59
|
+
# Returns one or more hashes based on the parameter supplied
|
60
|
+
#
|
61
|
+
# @overload generate()
|
62
|
+
# Returns one hash when called with no parameters or a parameter of 1
|
63
|
+
#
|
64
|
+
# @param [optional, Integer] quantity determines number of hashes returned (must be nil, 0 or 1)
|
65
|
+
# @return [String]
|
66
|
+
#
|
67
|
+
# @overload generate(quantity)
|
68
|
+
# Returns an array of hashes when called with a parameter greater than 1
|
69
|
+
#
|
70
|
+
# @param [Integer] quantity determines number of hashes returned (must be greater than 1)
|
71
|
+
# @return [Array<String>]
|
72
|
+
#
|
73
|
+
# @overload generate(quantity,secure_random)
|
74
|
+
# Returns an array of hashes when called with a parameter greater than 1
|
75
|
+
#
|
76
|
+
# @param [Integer] quantity determines number of hashes returned (must be greater than 1)
|
77
|
+
# @param [Boolean] secure_random attempts to use SecureRandom if set to True (Ruby 1.9.2 and up; reverts to Kernel#rand if SecureRandom is not supported)
|
78
|
+
def generate(quantity=1,secure_random=false)
|
79
|
+
@use_secure_random = secure_random
|
80
|
+
@fingerprint = get_fingerprint # only need to get the fingerprint once because it is constant per-run
|
81
|
+
return api unless quantity > 1
|
82
|
+
|
83
|
+
values = Array(1.upto(quantity)) # create an array of the correct size
|
84
|
+
return values.collect { api } # fill array with hashes
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Validates (minimally) the supplied string is in the correct format to be a hash
|
89
|
+
#
|
90
|
+
# Validation checks that the first letter is correct and that the rest of the
|
91
|
+
# string is the correct length and consists of lower case letters and numbers.
|
92
|
+
#
|
93
|
+
# @param [String] str string to check
|
94
|
+
# @return [Boolean] returns true if the format is correct
|
95
|
+
def validate(str)
|
96
|
+
blen = BLOCK_SIZE * 6
|
97
|
+
!!str.match(/#{LETTER}[a-z0-9]{#{blen}}/)
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
##
|
103
|
+
# Collects and asssembles the pieces into the actual hash string.
|
104
|
+
#
|
105
|
+
# @private
|
106
|
+
def api
|
107
|
+
timestamp = (Time.now.to_f * 1000).truncate.to_s(BASE)
|
108
|
+
|
109
|
+
random = get_random_block
|
110
|
+
|
111
|
+
@count = @count % DISCRETE_VALUES
|
112
|
+
counter = pad(@count.to_s(BASE))
|
113
|
+
|
114
|
+
@count += 1
|
115
|
+
|
116
|
+
return (LETTER + timestamp + counter + @fingerprint + random)
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Returns a string which has been converted to the correct size alphabet as defined in
|
121
|
+
# the BASE constant (e.g. base36) and then padded or trimmed to the correct length.
|
122
|
+
#
|
123
|
+
# @private
|
124
|
+
def format(text,size=BLOCK_SIZE)
|
125
|
+
base36_text = text.to_s(BASE)
|
126
|
+
return (base36_text.length > size) ? trim(base36_text,size) : pad(base36_text,size)
|
127
|
+
end
|
128
|
+
|
129
|
+
##
|
130
|
+
# Returns a string trimmed to the length supplied or BLOCK_SIZE if no length is supplied.
|
131
|
+
#
|
132
|
+
# @private
|
133
|
+
def trim(text,max=BLOCK_SIZE)
|
134
|
+
original_length = text.length
|
135
|
+
return text[original_length-max,max]
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Returns a string right-justified and padded with zeroes up to the length supplied or
|
140
|
+
# BLOCK_SIZE if no length is supplied.
|
141
|
+
#
|
142
|
+
# @private
|
143
|
+
def pad(text,size=BLOCK_SIZE)
|
144
|
+
return text.rjust(size,"0")
|
145
|
+
end
|
146
|
+
|
147
|
+
##
|
148
|
+
# Generates a random string.
|
149
|
+
#
|
150
|
+
# @private
|
151
|
+
def get_random_block
|
152
|
+
@secure_random = defined?(SecureRandom) if @secure_random.nil?
|
153
|
+
if @secure_random && @use_secure_random then
|
154
|
+
number = SecureRandom.random_number(RAND_MAX - RAND_MIN) + RAND_MIN
|
155
|
+
else
|
156
|
+
number = ((rand * (RAND_MAX - RAND_MIN)) + RAND_MIN)
|
157
|
+
end
|
158
|
+
return number.truncate.to_s(BASE)
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Assembles the host fingerprint based on the hostname and the PID.
|
163
|
+
#
|
164
|
+
# @private
|
165
|
+
def get_fingerprint
|
166
|
+
padding = 2
|
167
|
+
hostname = Socket.gethostname
|
168
|
+
hostid = hostname.split('').inject(hostname.length+BASE) do |a,i|
|
169
|
+
a += (i.respond_to? "ord") ? i.ord : i[0]
|
170
|
+
end
|
171
|
+
return format($$, padding) + format(hostid, padding)
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|
data/lib/cuid/version.rb
ADDED
data/test/test_cuid.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'cuid'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
class CuidTest < Test::Unit::TestCase
|
5
|
+
def test_generate_string
|
6
|
+
c = Cuid::generate
|
7
|
+
assert c.is_a? String
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_generate_array
|
11
|
+
c = Cuid::generate(3)
|
12
|
+
assert c.is_a? Array
|
13
|
+
assert_equal 3, c.size
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_generate_format
|
17
|
+
c = Cuid::generate
|
18
|
+
assert_equal 25, c.length
|
19
|
+
assert c.start_with? "c"
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_validate_true
|
23
|
+
c = Cuid::generate
|
24
|
+
assert Cuid::validate(c)
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_valiate_false
|
28
|
+
c = "d00000000000000000000"
|
29
|
+
assert !Cuid::validate(c)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_collision
|
33
|
+
results = {}
|
34
|
+
collision = false
|
35
|
+
c = Cuid::generate(600000)
|
36
|
+
c.each do |e|
|
37
|
+
collision = true if results[e]
|
38
|
+
results[e] = true
|
39
|
+
end
|
40
|
+
assert !collision
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_secure_random
|
44
|
+
results = {}
|
45
|
+
collision = false
|
46
|
+
c = Cuid::generate(1000,true)
|
47
|
+
c.each do |e|
|
48
|
+
collision = true if results[e]
|
49
|
+
results[e] = true
|
50
|
+
end
|
51
|
+
assert !collision
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_version
|
55
|
+
assert_nothing_raised { Cuid::VERSION }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cuid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ian Shannon
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-15 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Ruby implementation of Eric Elliot's javascript cuid
|
15
|
+
email:
|
16
|
+
- iyshannon@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- .rbenv-version
|
23
|
+
- .travis.yml
|
24
|
+
- .yardopts
|
25
|
+
- Gemfile
|
26
|
+
- README.md
|
27
|
+
- Rakefile
|
28
|
+
- cuid.gemspec
|
29
|
+
- doc/index.html
|
30
|
+
- lib/cuid.rb
|
31
|
+
- lib/cuid/version.rb
|
32
|
+
- test/test_cuid.rb
|
33
|
+
homepage: http://github.com/iyshannon/cuid
|
34
|
+
licenses: []
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 1.8.24
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: Collision-resistant ids optimized for horizontal scaling and performance
|
57
|
+
test_files: []
|
58
|
+
has_rdoc:
|