cuid 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,3 @@
1
+ module Cuid
2
+ VERSION = "1.0.0"
3
+ end
@@ -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: