cuid 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: