opaque_id 1.4.0 → 1.7.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.
data/docs/algorithms.md CHANGED
@@ -8,7 +8,7 @@ permalink: /algorithms/
8
8
 
9
9
  # Algorithms
10
10
 
11
- OpaqueId uses sophisticated algorithms to generate cryptographically secure, collision-free opaque IDs. This guide explains the technical details behind the generation process, optimization strategies, and mathematical foundations.
11
+ OpaqueId builds on Ruby's built-in `SecureRandom` methods to generate cryptographically secure, collision-free opaque IDs. This guide explains the technical details behind the generation process, optimization strategies, and mathematical foundations.
12
12
 
13
13
  - TOC
14
14
  {:toc}
@@ -45,10 +45,10 @@ end
45
45
 
46
46
  ### Key Features
47
47
 
48
- - **Bitwise Operations**: Uses `byte & 63` instead of `byte % 64` for faster computation
48
+ - **Bitwise Operations**: Uses `byte & 63` instead of `byte % 64` for efficient computation
49
49
  - **No Modulo Bias**: 64 is a power of 2, so bitwise AND provides uniform distribution
50
50
  - **Single Random Call**: One `SecureRandom.random_bytes(1)` call per character
51
- - **Maximum Performance**: Optimized for speed with 64-character alphabets
51
+ - **Performance Optimized**: Designed for speed with 64-character alphabets
52
52
 
53
53
  ### Mathematical Foundation
54
54
 
@@ -60,7 +60,7 @@ For a 64-character alphabet:
60
60
 
61
61
  ### Performance Characteristics
62
62
 
63
- - **Optimized for speed**: Uses bitwise operations for maximum performance
63
+ - **Optimized for speed**: Uses bitwise operations for efficient performance
64
64
  - **No rejection sampling**: All generated bytes are used efficiently
65
65
  - **Linear time complexity**: O(n) where n is the ID length
66
66
 
@@ -19,20 +19,20 @@ The main module for generating opaque IDs.
19
19
 
20
20
  ### Constants
21
21
 
22
- #### ALPHANUMERIC_ALPHABET
22
+ #### SLUG_LIKE_ALPHABET
23
23
 
24
24
  Default alphabet for ID generation.
25
25
 
26
26
  ```ruby
27
- OpaqueId::ALPHANUMERIC_ALPHABET
28
- # => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
27
+ OpaqueId::SLUG_LIKE_ALPHABET
28
+ # => "0123456789abcdefghijklmnopqrstuvwxyz"
29
29
  ```
30
30
 
31
31
  **Characteristics:**
32
32
 
33
- - **Length**: 62 characters
34
- - **Characters**: A-Z, a-z, 0-9
35
- - **Use case**: General purpose, URL-safe
33
+ - **Length**: 36 characters
34
+ - **Characters**: 0-9, a-z
35
+ - **Use case**: URL-friendly, double-click selectable
36
36
  - **Performance**: Good
37
37
 
38
38
  #### STANDARD_ALPHABET
@@ -0,0 +1,385 @@
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.
@@ -26,10 +26,10 @@ class User < ApplicationRecord
26
26
  # Custom column name
27
27
  self.opaque_id_column = :public_id
28
28
 
29
- # Custom length (default: 21)
29
+ # Custom length (default: 18)
30
30
  self.opaque_id_length = 15
31
31
 
32
- # Custom alphabet (default: ALPHANUMERIC_ALPHABET)
32
+ # Custom alphabet (default: SLUG_LIKE_ALPHABET)
33
33
  self.opaque_id_alphabet = OpaqueId::STANDARD_ALPHABET
34
34
 
35
35
  # Require letter start (default: false)
@@ -45,14 +45,14 @@ end
45
45
 
46
46
  ### Configuration Options Reference
47
47
 
48
- | Option | Type | Default | Description |
49
- | -------------------------------- | ------- | ----------------------- | --------------------------------------------- |
50
- | `opaque_id_column` | Symbol | `:opaque_id` | Column name for storing the opaque ID |
51
- | `opaque_id_length` | Integer | `21` | Length of generated IDs |
52
- | `opaque_id_alphabet` | String | `ALPHANUMERIC_ALPHABET` | Character set for ID generation |
53
- | `opaque_id_require_letter_start` | Boolean | `false` | Require IDs to start with a letter |
54
- | `opaque_id_max_retry` | Integer | `3` | Maximum retry attempts for collision handling |
55
- | `opaque_id_purge_chars` | Array | `[]` | Characters to exclude from generated IDs |
48
+ | Option | Type | Default | Description |
49
+ | -------------------------------- | ------- | -------------------- | --------------------------------------------- |
50
+ | `opaque_id_column` | Symbol | `:opaque_id` | Column name for storing the opaque ID |
51
+ | `opaque_id_length` | Integer | `18` | Length of generated IDs |
52
+ | `opaque_id_alphabet` | String | `SLUG_LIKE_ALPHABET` | Character set for ID generation |
53
+ | `opaque_id_require_letter_start` | Boolean | `false` | Require IDs to start with a letter |
54
+ | `opaque_id_max_retry` | Integer | `3` | Maximum retry attempts for collision handling |
55
+ | `opaque_id_purge_chars` | Array | `[]` | Characters to exclude from generated IDs |
56
56
 
57
57
  ## Global Configuration
58
58
 
@@ -97,12 +97,25 @@ end
97
97
 
98
98
  ### Built-in Alphabets
99
99
 
100
- #### ALPHANUMERIC_ALPHABET (Default)
100
+ #### SLUG_LIKE_ALPHABET (Default)
101
+
102
+ ```ruby
103
+ # Characters: 0-9, a-z (36 characters)
104
+ # Use case: URL-safe, double-click selectable, no confusing characters
105
+ # Example output: "izkpm55j334u8x9y2a"
106
+
107
+ class User < ApplicationRecord
108
+ include OpaqueId::Model
109
+ self.opaque_id_alphabet = OpaqueId::SLUG_LIKE_ALPHABET
110
+ end
111
+ ```
112
+
113
+ #### ALPHANUMERIC_ALPHABET
101
114
 
102
115
  ```ruby
103
116
  # Characters: A-Z, a-z, 0-9 (62 characters)
104
117
  # Use case: General purpose, URL-safe
105
- # Example output: "V1StGXR8_Z5jdHi6B-myT"
118
+ # Example output: "V1StGXR8Z5jdHi6BmyT"
106
119
 
107
120
  class User < ApplicationRecord
108
121
  include OpaqueId::Model
@@ -240,9 +253,9 @@ end
240
253
  # Now the methods use the custom column name
241
254
  user = User.create!(name: "John Doe")
242
255
  puts user.public_id
243
- # => "V1StGXR8_Z5jdHi6B-myT"
256
+ # => "izkpm55j334u8x9y2a"
244
257
 
245
- user = User.find_by_public_id("V1StGXR8_Z5jdHi6B-myT")
258
+ user = User.find_by_public_id("izkpm55j334u8x9y2a")
246
259
  ```
247
260
 
248
261
  ### Multiple Column Names
@@ -279,7 +292,7 @@ end
279
292
  # This will retry until it generates an ID starting with a letter
280
293
  user = User.create!(name: "John Doe")
281
294
  puts user.opaque_id
282
- # => "V1StGXR8_Z5jdHi6B-myT" (starts with 'V')
295
+ # => "izkpm55j334u8x9y2a" (starts with 'i')
283
296
  ```
284
297
 
285
298
  ### Character Purging
@@ -295,7 +308,7 @@ end
295
308
  # Generated IDs will not contain these characters
296
309
  user = User.create!(name: "John Doe")
297
310
  puts user.opaque_id
298
- # => "V1StGXR8_Z5jdHi6B-myT" (no '0', 'O', 'l', 'I')
311
+ # => "izkpm55j334u8x9y2a" (no '0', 'O', 'l', 'I')
299
312
  ```
300
313
 
301
314
  ## Collision Handling Configuration
@@ -496,8 +509,8 @@ class UserTest < ActiveSupport::TestCase
496
509
  user = User.new(name: "Test User")
497
510
 
498
511
  assert user.valid?
499
- assert_equal 21, user.class.opaque_id_length
500
- assert_equal OpaqueId::ALPHANUMERIC_ALPHABET, user.class.opaque_id_alphabet
512
+ assert_equal 18, user.class.opaque_id_length
513
+ assert_equal OpaqueId::SLUG_LIKE_ALPHABET, user.class.opaque_id_alphabet
501
514
  end
502
515
 
503
516
  test "opaque_id generation works with custom configuration" do
@@ -523,6 +536,7 @@ end
523
536
 
524
537
  ### 2. Select Suitable Alphabets
525
538
 
539
+ - **SLUG_LIKE_ALPHABET** (default): URL-safe, double-click selectable, no confusing characters
526
540
  - **ALPHANUMERIC_ALPHABET**: General purpose, URL-safe
527
541
  - **STANDARD_ALPHABET**: Fastest generation, URL-safe
528
542
  - **Custom alphabets**: Specific requirements (numeric, hexadecimal, etc.)
data/docs/index.md CHANGED
@@ -12,7 +12,7 @@ permalink: /
12
12
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop)
13
13
  [![Gem Downloads](https://img.shields.io/badge/gem%20downloads-opaque_id-blue)](https://rubygems.org/gems/opaque_id)
14
14
 
15
- A Ruby gem for generating cryptographically secure, collision-free opaque IDs for ActiveRecord models. OpaqueId provides a drop-in replacement for `nanoid.rb` using Ruby's built-in `SecureRandom` methods with optimized algorithms for unbiased distribution.
15
+ A simple Ruby gem for generating secure, opaque IDs for ActiveRecord models. OpaqueId provides a drop-in replacement for `nanoid.rb` using Ruby's built-in `SecureRandom` methods, with slug-like IDs as the default for optimal URL safety and user experience.
16
16
 
17
17
  - TOC
18
18
  {:toc}
@@ -57,10 +57,10 @@ end
57
57
 
58
58
  # Create a user - opaque_id is automatically generated
59
59
  user = User.create!(name: "John Doe")
60
- puts user.opaque_id # => "V1StGXR8_Z5jdHi6B-myT"
60
+ puts user.opaque_id # => "izkpm55j334u8x9y2a"
61
61
 
62
62
  # Find by opaque_id
63
- user = User.find_by_opaque_id("V1StGXR8_Z5jdHi6B-myT")
63
+ user = User.find_by_opaque_id("izkpm55j334u8x9y2a")
64
64
  ```
65
65
 
66
66
  ## Why OpaqueId?
@@ -128,6 +128,24 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
128
128
 
129
129
  This project follows an "open source, closed contribution" model. We welcome bug reports, feature requests, and documentation improvements through GitHub Issues.
130
130
 
131
+ ## Performance & Benchmarks
132
+
133
+ You can run benchmarks to test OpaqueId's performance and uniqueness characteristics on your system.
134
+
135
+ **Quick Test:**
136
+
137
+ ```bash
138
+ # Test 10,000 ID generation
139
+ ruby -e "require 'opaque_id'; start=Time.now; 10000.times{OpaqueId.generate}; puts \"Generated 10,000 IDs in #{(Time.now-start).round(4)}s\""
140
+ ```
141
+
142
+ **Expected Results:**
143
+
144
+ - **Performance**: 100,000+ IDs per second on modern hardware
145
+ - **Uniqueness**: Zero collisions in practice (theoretical probability < 10^-16 for 1M IDs)
146
+
147
+ For comprehensive benchmarks including collision tests, alphabet distribution analysis, and performance comparisons, see the [Benchmarks Guide](benchmarks.md).
148
+
131
149
  ## Acknowledgements
132
150
 
133
151
  - [nanoid.rb](https://github.com/radeno/nanoid.rb) - Original inspiration and reference implementation
data/docs/performance.md CHANGED
@@ -8,7 +8,7 @@ permalink: /performance/
8
8
 
9
9
  # Performance
10
10
 
11
- OpaqueId is designed for high performance with optimized algorithms and efficient memory usage. This guide covers benchmarks, optimization strategies, and scalability considerations.
11
+ OpaqueId is designed for efficient ID generation with optimized algorithms and memory usage. This guide covers performance characteristics, optimization strategies, and scalability considerations.
12
12
 
13
13
  - TOC
14
14
  {:toc}
@@ -17,11 +17,11 @@ OpaqueId is designed for high performance with optimized algorithms and efficien
17
17
 
18
18
  ### Generation Speed
19
19
 
20
- OpaqueId is designed for high performance with optimized algorithms for different alphabet sizes and ID lengths.
20
+ OpaqueId is designed for efficient ID generation with optimized algorithms for different alphabet sizes and ID lengths.
21
21
 
22
22
  #### Algorithm Performance
23
23
 
24
- - **Fast Path (64-character alphabets)**: Uses bitwise operations for maximum speed with no rejection sampling overhead
24
+ - **Fast Path (64-character alphabets)**: Uses bitwise operations for efficient generation with no rejection sampling overhead
25
25
  - **Unbiased Path (Other alphabets)**: Uses rejection sampling for unbiased distribution with slight performance overhead
26
26
  - **Performance scales linearly** with ID length and batch size
27
27