opaque_id 1.7.0 → 1.7.7
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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/lib/opaque_id/version.rb +1 -1
- metadata +2 -35
- data/.cursor/rules/create-prd.md +0 -56
- data/.cursor/rules/generate-tasks.md +0 -60
- data/.cursor/rules/process-task-list.md +0 -47
- data/.release-please-manifest.json +0 -3
- data/.rubocop.yml +0 -31
- data/CHANGELOG.md +0 -445
- data/CODE_OF_CONDUCT.md +0 -132
- data/README.md +0 -1698
- data/docs/.gitignore +0 -5
- data/docs/404.html +0 -25
- data/docs/Gemfile +0 -31
- data/docs/Gemfile.lock +0 -335
- data/docs/_config.yml +0 -153
- data/docs/_data/navigation.yml +0 -132
- data/docs/_includes/footer_custom.html +0 -8
- data/docs/_includes/head_custom.html +0 -67
- data/docs/algorithms.md +0 -412
- data/docs/alphabets.md +0 -524
- data/docs/api-reference.md +0 -597
- data/docs/assets/css/custom.scss +0 -751
- data/docs/assets/images/favicon.svg +0 -17
- data/docs/assets/images/og-image.svg +0 -65
- data/docs/benchmarks.md +0 -385
- data/docs/configuration.md +0 -565
- data/docs/development.md +0 -570
- data/docs/getting-started.md +0 -259
- data/docs/index.md +0 -153
- data/docs/installation.md +0 -380
- data/docs/performance.md +0 -491
- data/docs/robots.txt +0 -11
- data/docs/security.md +0 -601
- data/docs/usage.md +0 -421
- data/docs/use-cases.md +0 -572
- data/release-please-config.json +0 -56
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
|
|
2
|
-
<defs>
|
|
3
|
-
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
-
<stop offset="0%" style="stop-color:#a855f7;stop-opacity:1" />
|
|
5
|
-
<stop offset="100%" style="stop-color:#c084fc;stop-opacity:1" />
|
|
6
|
-
</linearGradient>
|
|
7
|
-
</defs>
|
|
8
|
-
|
|
9
|
-
<!-- Background circle -->
|
|
10
|
-
<circle cx="16" cy="16" r="15" fill="#0a0a0a" stroke="url(#gradient)" stroke-width="2"/>
|
|
11
|
-
|
|
12
|
-
<!-- Letter O -->
|
|
13
|
-
<circle cx="16" cy="16" r="8" fill="none" stroke="url(#gradient)" stroke-width="2.5"/>
|
|
14
|
-
|
|
15
|
-
<!-- Inner dot -->
|
|
16
|
-
<circle cx="16" cy="16" r="3" fill="url(#gradient)"/>
|
|
17
|
-
</svg>
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" width="1200" height="630">
|
|
2
|
-
<defs>
|
|
3
|
-
<linearGradient id="bgGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
-
<stop offset="0%" style="stop-color:#0a0a0a;stop-opacity:1" />
|
|
5
|
-
<stop offset="100%" style="stop-color:#1a1a1a;stop-opacity:1" />
|
|
6
|
-
</linearGradient>
|
|
7
|
-
<linearGradient id="textGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
8
|
-
<stop offset="0%" style="stop-color:#a855f7;stop-opacity:1" />
|
|
9
|
-
<stop offset="100%" style="stop-color:#c084fc;stop-opacity:1" />
|
|
10
|
-
</linearGradient>
|
|
11
|
-
</defs>
|
|
12
|
-
|
|
13
|
-
<!-- Background -->
|
|
14
|
-
<rect width="1200" height="630" fill="url(#bgGradient)"/>
|
|
15
|
-
|
|
16
|
-
<!-- Logo -->
|
|
17
|
-
<circle cx="150" cy="150" r="60" fill="none" stroke="url(#textGradient)" stroke-width="8"/>
|
|
18
|
-
<circle cx="150" cy="150" r="25" fill="url(#textGradient)"/>
|
|
19
|
-
|
|
20
|
-
<!-- Title -->
|
|
21
|
-
<text x="300" y="180" font-family="Arial, sans-serif" font-size="72" font-weight="bold" fill="url(#textGradient)">
|
|
22
|
-
OpaqueId
|
|
23
|
-
</text>
|
|
24
|
-
|
|
25
|
-
<!-- Subtitle -->
|
|
26
|
-
<text x="300" y="240" font-family="Arial, sans-serif" font-size="32" fill="#e0e0e0">
|
|
27
|
-
Documentation
|
|
28
|
-
</text>
|
|
29
|
-
|
|
30
|
-
<!-- Description -->
|
|
31
|
-
<text x="300" y="320" font-family="Arial, sans-serif" font-size="24" fill="#b0b0b0">
|
|
32
|
-
Generate cryptographically secure, collision-free opaque IDs
|
|
33
|
-
</text>
|
|
34
|
-
<text x="300" y="360" font-family="Arial, sans-serif" font-size="24" fill="#b0b0b0">
|
|
35
|
-
for ActiveRecord models
|
|
36
|
-
</text>
|
|
37
|
-
|
|
38
|
-
<!-- Features -->
|
|
39
|
-
<text x="300" y="420" font-family="Arial, sans-serif" font-size="18" fill="#808080">
|
|
40
|
-
• High Performance • Rails Integration • Secure Generation
|
|
41
|
-
</text>
|
|
42
|
-
|
|
43
|
-
<!-- URL -->
|
|
44
|
-
<text x="300" y="480" font-family="Arial, sans-serif" font-size="20" fill="#a855f7">
|
|
45
|
-
nyaggah.github.io/opaque_id
|
|
46
|
-
</text>
|
|
47
|
-
|
|
48
|
-
<!-- Code example -->
|
|
49
|
-
<rect x="800" y="200" width="350" height="200" fill="#1a1a1a" stroke="#333333" stroke-width="2" rx="8"/>
|
|
50
|
-
<text x="820" y="230" font-family="Monaco, monospace" font-size="16" fill="#e0e0e0">
|
|
51
|
-
class User < ApplicationRecord
|
|
52
|
-
</text>
|
|
53
|
-
<text x="820" y="260" font-family="Monaco, monospace" font-size="16" fill="#e0e0e0">
|
|
54
|
-
include OpaqueId::Model
|
|
55
|
-
</text>
|
|
56
|
-
<text x="820" y="290" font-family="Monaco, monospace" font-size="16" fill="#e0e0e0">
|
|
57
|
-
end
|
|
58
|
-
</text>
|
|
59
|
-
<text x="820" y="330" font-family="Monaco, monospace" font-size="16" fill="#a855f7">
|
|
60
|
-
# user.opaque_id
|
|
61
|
-
</text>
|
|
62
|
-
<text x="820" y="360" font-family="Monaco, monospace" font-size="16" fill="#10b981">
|
|
63
|
-
# => "V1StGXR8_Z5jdHi6B-myT"
|
|
64
|
-
</text>
|
|
65
|
-
</svg>
|
data/docs/benchmarks.md
DELETED
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
# OpaqueId Benchmarks
|
|
2
|
-
|
|
3
|
-
This document provides benchmark scripts that you can run to test OpaqueId's performance and uniqueness characteristics on your own system.
|
|
4
|
-
|
|
5
|
-
## Performance Benchmarks
|
|
6
|
-
|
|
7
|
-
### SecureRandom Comparison Test
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
#!/usr/bin/env ruby
|
|
11
|
-
|
|
12
|
-
require 'opaque_id'
|
|
13
|
-
require 'securerandom'
|
|
14
|
-
|
|
15
|
-
puts "OpaqueId vs SecureRandom Comparison"
|
|
16
|
-
puts "=" * 50
|
|
17
|
-
|
|
18
|
-
# Test different Ruby standard library methods
|
|
19
|
-
methods = {
|
|
20
|
-
'OpaqueId.generate' => -> { OpaqueId.generate },
|
|
21
|
-
'SecureRandom.urlsafe_base64' => -> { SecureRandom.urlsafe_base64 },
|
|
22
|
-
'SecureRandom.urlsafe_base64(16)' => -> { SecureRandom.urlsafe_base64(16) },
|
|
23
|
-
'SecureRandom.hex(9)' => -> { SecureRandom.hex(9) },
|
|
24
|
-
'SecureRandom.alphanumeric(18)' => -> { SecureRandom.alphanumeric(18) }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
count = 10000
|
|
28
|
-
|
|
29
|
-
puts "Performance comparison (#{count} IDs each):"
|
|
30
|
-
puts "-" * 50
|
|
31
|
-
|
|
32
|
-
methods.each do |name, method|
|
|
33
|
-
start_time = Time.now
|
|
34
|
-
ids = count.times.map { method.call }
|
|
35
|
-
end_time = Time.now
|
|
36
|
-
duration = end_time - start_time
|
|
37
|
-
rate = (count / duration).round(0)
|
|
38
|
-
|
|
39
|
-
# Check uniqueness
|
|
40
|
-
unique_count = ids.uniq.length
|
|
41
|
-
collisions = count - unique_count
|
|
42
|
-
|
|
43
|
-
# Check characteristics
|
|
44
|
-
sample_id = ids.first
|
|
45
|
-
length = sample_id.length
|
|
46
|
-
has_uppercase = sample_id.match?(/[A-Z]/)
|
|
47
|
-
has_lowercase = sample_id.match?(/[a-z]/)
|
|
48
|
-
has_numbers = sample_id.match?(/[0-9]/)
|
|
49
|
-
has_special = sample_id.match?(/[^A-Za-z0-9]/)
|
|
50
|
-
|
|
51
|
-
puts "#{name.ljust(30)}: #{duration.round(4)}s (#{rate} IDs/sec)"
|
|
52
|
-
puts " Length: #{length}, Collisions: #{collisions}"
|
|
53
|
-
puts " Sample: '#{sample_id}'"
|
|
54
|
-
puts " Chars: #{has_uppercase ? 'A-Z' : ''}#{has_lowercase ? 'a-z' : ''}#{has_numbers ? '0-9' : ''}#{has_special ? 'special' : ''}"
|
|
55
|
-
puts
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
puts "Comparison completed."
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Basic Performance Test
|
|
62
|
-
|
|
63
|
-
```ruby
|
|
64
|
-
#!/usr/bin/env ruby
|
|
65
|
-
|
|
66
|
-
require 'opaque_id'
|
|
67
|
-
|
|
68
|
-
puts "OpaqueId Performance Benchmark"
|
|
69
|
-
puts "=" * 40
|
|
70
|
-
|
|
71
|
-
# Test different batch sizes
|
|
72
|
-
[100, 1000, 10000, 100000].each do |count|
|
|
73
|
-
start_time = Time.now
|
|
74
|
-
count.times { OpaqueId.generate }
|
|
75
|
-
end_time = Time.now
|
|
76
|
-
duration = end_time - start_time
|
|
77
|
-
rate = (count / duration).round(0)
|
|
78
|
-
|
|
79
|
-
puts "#{count.to_s.rjust(6)} IDs: #{duration.round(4)}s (#{rate} IDs/sec)"
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
puts "\nPerformance test completed."
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Alphabet Performance Comparison
|
|
86
|
-
|
|
87
|
-
```ruby
|
|
88
|
-
#!/usr/bin/env ruby
|
|
89
|
-
|
|
90
|
-
require 'opaque_id'
|
|
91
|
-
|
|
92
|
-
puts "Alphabet Performance Comparison"
|
|
93
|
-
puts "=" * 40
|
|
94
|
-
|
|
95
|
-
alphabets = {
|
|
96
|
-
'SLUG_LIKE_ALPHABET' => OpaqueId::SLUG_LIKE_ALPHABET,
|
|
97
|
-
'ALPHANUMERIC_ALPHABET' => OpaqueId::ALPHANUMERIC_ALPHABET,
|
|
98
|
-
'STANDARD_ALPHABET' => OpaqueId::STANDARD_ALPHABET
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
count = 10000
|
|
102
|
-
|
|
103
|
-
alphabets.each do |name, alphabet|
|
|
104
|
-
start_time = Time.now
|
|
105
|
-
count.times { OpaqueId.generate(alphabet: alphabet) }
|
|
106
|
-
end_time = Time.now
|
|
107
|
-
duration = end_time - start_time
|
|
108
|
-
rate = (count / duration).round(0)
|
|
109
|
-
|
|
110
|
-
puts "#{name.ljust(20)}: #{duration.round(4)}s (#{rate} IDs/sec)"
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
puts "\nAlphabet comparison completed."
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Size Performance Test
|
|
117
|
-
|
|
118
|
-
```ruby
|
|
119
|
-
#!/usr/bin/env ruby
|
|
120
|
-
|
|
121
|
-
require 'opaque_id'
|
|
122
|
-
|
|
123
|
-
puts "Size Performance Test"
|
|
124
|
-
puts "=" * 40
|
|
125
|
-
|
|
126
|
-
sizes = [8, 12, 18, 24, 32, 48, 64]
|
|
127
|
-
count = 10000
|
|
128
|
-
|
|
129
|
-
sizes.each do |size|
|
|
130
|
-
start_time = Time.now
|
|
131
|
-
count.times { OpaqueId.generate(size: size) }
|
|
132
|
-
end_time = Time.now
|
|
133
|
-
duration = end_time - start_time
|
|
134
|
-
rate = (count / duration).round(0)
|
|
135
|
-
|
|
136
|
-
puts "Size #{size.to_s.rjust(2)}: #{duration.round(4)}s (#{rate} IDs/sec)"
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
puts "\nSize performance test completed."
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## Uniqueness Tests
|
|
143
|
-
|
|
144
|
-
### Collision Probability Test
|
|
145
|
-
|
|
146
|
-
```ruby
|
|
147
|
-
#!/usr/bin/env ruby
|
|
148
|
-
|
|
149
|
-
require 'opaque_id'
|
|
150
|
-
|
|
151
|
-
puts "Collision Probability Test"
|
|
152
|
-
puts "=" * 40
|
|
153
|
-
|
|
154
|
-
# Test different sample sizes
|
|
155
|
-
[1000, 10000, 100000].each do |count|
|
|
156
|
-
puts "\nTesting #{count} IDs..."
|
|
157
|
-
|
|
158
|
-
start_time = Time.now
|
|
159
|
-
ids = count.times.map { OpaqueId.generate }
|
|
160
|
-
end_time = Time.now
|
|
161
|
-
|
|
162
|
-
unique_ids = ids.uniq
|
|
163
|
-
collisions = count - unique_ids.length
|
|
164
|
-
collision_rate = (collisions.to_f / count * 100).round(6)
|
|
165
|
-
|
|
166
|
-
puts " Generated: #{count} IDs in #{(end_time - start_time).round(4)}s"
|
|
167
|
-
puts " Unique: #{unique_ids.length} IDs"
|
|
168
|
-
puts " Collisions: #{collisions} (#{collision_rate}%)"
|
|
169
|
-
puts " Uniqueness: #{collision_rate == 0 ? '✅ Perfect' : '⚠️ Collisions detected'}"
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
puts "\nCollision test completed."
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
### Birthday Paradox Test
|
|
176
|
-
|
|
177
|
-
```ruby
|
|
178
|
-
#!/usr/bin/env ruby
|
|
179
|
-
|
|
180
|
-
require 'opaque_id'
|
|
181
|
-
|
|
182
|
-
puts "Birthday Paradox Test"
|
|
183
|
-
puts "=" * 40
|
|
184
|
-
|
|
185
|
-
# Test the birthday paradox with different sample sizes
|
|
186
|
-
# For 18-character slug-like alphabet (36 chars), we have 36^18 possible combinations
|
|
187
|
-
# This is approximately 10^28, so collisions should be extremely rare
|
|
188
|
-
|
|
189
|
-
sample_sizes = [1000, 10000, 50000, 100000]
|
|
190
|
-
|
|
191
|
-
sample_sizes.each do |count|
|
|
192
|
-
puts "\nTesting #{count} IDs for birthday paradox..."
|
|
193
|
-
|
|
194
|
-
start_time = Time.now
|
|
195
|
-
ids = count.times.map { OpaqueId.generate }
|
|
196
|
-
end_time = Time.now
|
|
197
|
-
|
|
198
|
-
unique_ids = ids.uniq
|
|
199
|
-
collisions = count - unique_ids.length
|
|
200
|
-
|
|
201
|
-
# Calculate theoretical collision probability
|
|
202
|
-
# For 18-char slug-like alphabet: 36^18 ≈ 10^28 possible combinations
|
|
203
|
-
alphabet_size = 36
|
|
204
|
-
id_length = 18
|
|
205
|
-
total_possibilities = alphabet_size ** id_length
|
|
206
|
-
|
|
207
|
-
# Approximate birthday paradox probability
|
|
208
|
-
# P(collision) ≈ 1 - e^(-n(n-1)/(2*N)) where n=sample_size, N=total_possibilities
|
|
209
|
-
n = count
|
|
210
|
-
N = total_possibilities
|
|
211
|
-
theoretical_prob = 1 - Math.exp(-(n * (n - 1)) / (2.0 * N))
|
|
212
|
-
|
|
213
|
-
puts " Sample size: #{count}"
|
|
214
|
-
puts " Total possibilities: #{total_possibilities.to_s.reverse.gsub(/(\d{3})(?=.)/, '\1,').reverse}"
|
|
215
|
-
puts " Theoretical collision probability: #{theoretical_prob.round(20)}"
|
|
216
|
-
puts " Actual collisions: #{collisions}"
|
|
217
|
-
puts " Result: #{collisions == 0 ? '✅ No collisions (as expected)' : '⚠️ Collisions detected'}"
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
puts "\nBirthday paradox test completed."
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### Alphabet Distribution Test
|
|
224
|
-
|
|
225
|
-
```ruby
|
|
226
|
-
#!/usr/bin/env ruby
|
|
227
|
-
|
|
228
|
-
require 'opaque_id'
|
|
229
|
-
|
|
230
|
-
puts "Alphabet Distribution Test"
|
|
231
|
-
puts "=" * 40
|
|
232
|
-
|
|
233
|
-
# Test that all characters in the alphabet are used roughly equally
|
|
234
|
-
alphabet = OpaqueId::SLUG_LIKE_ALPHABET
|
|
235
|
-
count = 100000
|
|
236
|
-
|
|
237
|
-
puts "Testing distribution for #{alphabet.length}-character alphabet..."
|
|
238
|
-
puts "Sample size: #{count} IDs"
|
|
239
|
-
|
|
240
|
-
start_time = Time.now
|
|
241
|
-
ids = count.times.map { OpaqueId.generate }
|
|
242
|
-
end_time = Time.now
|
|
243
|
-
|
|
244
|
-
# Count character frequency
|
|
245
|
-
char_counts = Hash.new(0)
|
|
246
|
-
ids.each do |id|
|
|
247
|
-
id.each_char { |char| char_counts[char] += 1 }
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
total_chars = ids.join.length
|
|
251
|
-
expected_per_char = total_chars.to_f / alphabet.length
|
|
252
|
-
|
|
253
|
-
puts "\nCharacter distribution:"
|
|
254
|
-
puts "Character | Count | Expected | Deviation"
|
|
255
|
-
puts "-" * 45
|
|
256
|
-
|
|
257
|
-
alphabet.each_char do |char|
|
|
258
|
-
count = char_counts[char]
|
|
259
|
-
deviation = ((count - expected_per_char) / expected_per_char * 100).round(2)
|
|
260
|
-
puts "#{char.ljust(8)} | #{count.to_s.rjust(5)} | #{expected_per_char.round(1).to_s.rjust(8)} | #{deviation.to_s.rjust(6)}%"
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
# Calculate chi-square test for uniform distribution
|
|
264
|
-
chi_square = alphabet.each_char.sum do |char|
|
|
265
|
-
observed = char_counts[char]
|
|
266
|
-
expected = expected_per_char
|
|
267
|
-
((observed - expected) ** 2) / expected
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
puts "\nChi-square statistic: #{chi_square.round(4)}"
|
|
271
|
-
puts "Distribution: #{chi_square < 30 ? '✅ Appears uniform' : '⚠️ May not be uniform'}"
|
|
272
|
-
|
|
273
|
-
puts "\nDistribution test completed."
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
## Running the Benchmarks
|
|
277
|
-
|
|
278
|
-
### Quick Performance Test
|
|
279
|
-
|
|
280
|
-
```bash
|
|
281
|
-
# Run basic performance test
|
|
282
|
-
ruby -e "
|
|
283
|
-
require 'opaque_id'
|
|
284
|
-
puts 'OpaqueId Performance Test'
|
|
285
|
-
puts '=' * 30
|
|
286
|
-
[100, 1000, 10000].each do |count|
|
|
287
|
-
start = Time.now
|
|
288
|
-
count.times { OpaqueId.generate }
|
|
289
|
-
duration = Time.now - start
|
|
290
|
-
rate = (count / duration).round(0)
|
|
291
|
-
puts \"#{count.to_s.rjust(5)} IDs: #{duration.round(4)}s (#{rate} IDs/sec)\"
|
|
292
|
-
end
|
|
293
|
-
"
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
### Quick Uniqueness Test
|
|
297
|
-
|
|
298
|
-
```bash
|
|
299
|
-
# Run basic uniqueness test
|
|
300
|
-
ruby -e "
|
|
301
|
-
require 'opaque_id'
|
|
302
|
-
puts 'OpaqueId Uniqueness Test'
|
|
303
|
-
puts '=' * 30
|
|
304
|
-
count = 10000
|
|
305
|
-
ids = count.times.map { OpaqueId.generate }
|
|
306
|
-
unique = ids.uniq
|
|
307
|
-
collisions = count - unique.length
|
|
308
|
-
puts \"Generated: #{count} IDs\"
|
|
309
|
-
puts \"Unique: #{unique.length} IDs\"
|
|
310
|
-
puts \"Collisions: #{collisions}\"
|
|
311
|
-
puts \"Result: #{collisions == 0 ? 'Perfect uniqueness' : 'Collisions detected'}\"
|
|
312
|
-
"
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
## Expected Results
|
|
316
|
-
|
|
317
|
-
### Performance Expectations
|
|
318
|
-
|
|
319
|
-
On a modern system, you should expect:
|
|
320
|
-
|
|
321
|
-
- **100 IDs**: < 0.001s (100,000+ IDs/sec)
|
|
322
|
-
- **1,000 IDs**: < 0.01s (100,000+ IDs/sec)
|
|
323
|
-
- **10,000 IDs**: < 0.1s (100,000+ IDs/sec)
|
|
324
|
-
- **100,000 IDs**: < 1s (100,000+ IDs/sec)
|
|
325
|
-
|
|
326
|
-
### Uniqueness Expectations
|
|
327
|
-
|
|
328
|
-
- **1,000 IDs**: 0 collisions (100% unique)
|
|
329
|
-
- **10,000 IDs**: 0 collisions (100% unique)
|
|
330
|
-
- **100,000 IDs**: 0 collisions (100% unique)
|
|
331
|
-
- **1,000,000 IDs**: 0 collisions (100% unique)
|
|
332
|
-
|
|
333
|
-
The theoretical collision probability for 1 million IDs is approximately 10^-16, making collisions virtually impossible in practice.
|
|
334
|
-
|
|
335
|
-
## System Requirements
|
|
336
|
-
|
|
337
|
-
These benchmarks require:
|
|
338
|
-
|
|
339
|
-
- Ruby 2.7+ (for optimal performance)
|
|
340
|
-
- OpaqueId gem installed
|
|
341
|
-
- Sufficient memory for large sample sizes
|
|
342
|
-
|
|
343
|
-
For the largest tests (100,000+ IDs), ensure you have at least 100MB of available memory.
|
|
344
|
-
|
|
345
|
-
## Why Not Just Use SecureRandom?
|
|
346
|
-
|
|
347
|
-
Ruby's `SecureRandom` already provides secure random generation. Here's how OpaqueId compares:
|
|
348
|
-
|
|
349
|
-
### SecureRandom.urlsafe_base64 vs OpaqueId
|
|
350
|
-
|
|
351
|
-
| Feature | SecureRandom.urlsafe_base64 | OpaqueId.generate |
|
|
352
|
-
| ---------------------------- | ------------------------------- | ---------------------------- |
|
|
353
|
-
| **Length** | 22 characters (fixed) | 18 characters (configurable) |
|
|
354
|
-
| **Alphabet** | A-Z, a-z, 0-9, -, \_ (64 chars) | 0-9, a-z (36 chars) |
|
|
355
|
-
| **URL Safety** | ✅ Yes | ✅ Yes |
|
|
356
|
-
| **Double-click selectable** | ❌ No (contains special chars) | ✅ Yes (no special chars) |
|
|
357
|
-
| **Configurable length** | ❌ No | ✅ Yes |
|
|
358
|
-
| **Configurable alphabet** | ❌ No | ✅ Yes |
|
|
359
|
-
| **ActiveRecord integration** | ❌ Manual | ✅ Built-in |
|
|
360
|
-
| **Rails generator** | ❌ No | ✅ Yes |
|
|
361
|
-
|
|
362
|
-
### When to Use Each
|
|
363
|
-
|
|
364
|
-
**Use SecureRandom.urlsafe_base64 when:**
|
|
365
|
-
|
|
366
|
-
- You need maximum entropy (22 chars vs 18)
|
|
367
|
-
- You don't mind special characters (-, \_)
|
|
368
|
-
- You don't need double-click selection
|
|
369
|
-
- You're building a simple solution
|
|
370
|
-
|
|
371
|
-
**Use OpaqueId when:**
|
|
372
|
-
|
|
373
|
-
- You want slug-like IDs (no special characters)
|
|
374
|
-
- You need double-click selectable IDs
|
|
375
|
-
- You want configurable length and alphabet
|
|
376
|
-
- You're using ActiveRecord models
|
|
377
|
-
- You want consistent ID length (default: 18 characters)
|
|
378
|
-
|
|
379
|
-
### Performance Comparison
|
|
380
|
-
|
|
381
|
-
Run the [SecureRandom Comparison Test](#securerandom-comparison-test) to see how OpaqueId compares to various SecureRandom methods on your system.
|
|
382
|
-
|
|
383
|
-
### Migration from nanoid.rb
|
|
384
|
-
|
|
385
|
-
The nanoid.rb gem is [considered obsolete](https://github.com/radeno/nanoid.rb/issues/67) for Ruby 2.7+ because SecureRandom provides similar functionality. OpaqueId provides an alternative with different defaults and Rails integration.
|