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.
@@ -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 &lt; 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
- # =&gt; "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.