opaque_id 1.1.0 → 1.3.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,594 @@
1
+ ---
2
+ layout: default
3
+ title: API Reference
4
+ nav_order: 12
5
+ description: "Complete API documentation for OpaqueId methods and classes"
6
+ permalink: /api-reference/
7
+ ---
8
+
9
+ # API Reference
10
+
11
+ This document provides complete API documentation for OpaqueId, including all methods, classes, and configuration options.
12
+
13
+ ## Core Module: OpaqueId
14
+
15
+ The main module for generating opaque IDs.
16
+
17
+ ### Constants
18
+
19
+ #### ALPHANUMERIC_ALPHABET
20
+
21
+ Default alphabet for ID generation.
22
+
23
+ ```ruby
24
+ OpaqueId::ALPHANUMERIC_ALPHABET
25
+ # => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
26
+ ```
27
+
28
+ **Characteristics:**
29
+
30
+ - **Length**: 62 characters
31
+ - **Characters**: A-Z, a-z, 0-9
32
+ - **Use case**: General purpose, URL-safe
33
+ - **Performance**: Good
34
+
35
+ #### STANDARD_ALPHABET
36
+
37
+ Standard alphabet for high-performance generation.
38
+
39
+ ```ruby
40
+ OpaqueId::STANDARD_ALPHABET
41
+ # => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
42
+ ```
43
+
44
+ **Characteristics:**
45
+
46
+ - **Length**: 64 characters
47
+ - **Characters**: A-Z, a-z, 0-9, -, \_
48
+ - **Use case**: High performance, URL-safe
49
+ - **Performance**: Best (optimized path)
50
+
51
+ ### Methods
52
+
53
+ #### generate
54
+
55
+ Generates a cryptographically secure opaque ID.
56
+
57
+ ```ruby
58
+ OpaqueId.generate(size: 21, alphabet: ALPHANUMERIC_ALPHABET)
59
+ ```
60
+
61
+ **Parameters:**
62
+
63
+ | Parameter | Type | Default | Description |
64
+ | ---------- | ------- | ----------------------- | ---------------------------- |
65
+ | `size` | Integer | `21` | Length of the generated ID |
66
+ | `alphabet` | String | `ALPHANUMERIC_ALPHABET` | Character set for generation |
67
+
68
+ **Returns:** `String` - The generated opaque ID
69
+
70
+ **Raises:**
71
+
72
+ - `ConfigurationError` - If size is not positive or alphabet is empty
73
+ - `GenerationError` - If ID generation fails
74
+
75
+ **Examples:**
76
+
77
+ ```ruby
78
+ # Generate with default parameters
79
+ id = OpaqueId.generate
80
+ # => "V1StGXR8_Z5jdHi6B-myT"
81
+
82
+ # Generate with custom length
83
+ id = OpaqueId.generate(size: 10)
84
+ # => "V1StGXR8_Z5"
85
+
86
+ # Generate with custom alphabet
87
+ id = OpaqueId.generate(alphabet: OpaqueId::STANDARD_ALPHABET)
88
+ # => "V1StGXR8_Z5jdHi6B-myT"
89
+
90
+ # Generate with both custom parameters
91
+ id = OpaqueId.generate(size: 15, alphabet: "0123456789")
92
+ # => "123456789012345"
93
+ ```
94
+
95
+ **Algorithm Selection:**
96
+
97
+ - **Fast Path**: Used for 64-character alphabets (bitwise optimization)
98
+ - **Unbiased Path**: Used for other alphabets (rejection sampling)
99
+ - **Direct Repetition**: Used for single-character alphabets
100
+
101
+ ## ActiveRecord Concern: OpaqueId::Model
102
+
103
+ Provides ActiveRecord integration for automatic opaque ID generation.
104
+
105
+ ### Class Methods
106
+
107
+ #### opaque_id_column
108
+
109
+ Gets or sets the column name for storing opaque IDs.
110
+
111
+ ```ruby
112
+ self.opaque_id_column = :public_id
113
+ opaque_id_column
114
+ # => :public_id
115
+ ```
116
+
117
+ **Default:** `:opaque_id`
118
+
119
+ **Returns:** `Symbol` - The column name
120
+
121
+ #### opaque_id_length
122
+
123
+ Gets or sets the length of generated opaque IDs.
124
+
125
+ ```ruby
126
+ self.opaque_id_length = 15
127
+ opaque_id_length
128
+ # => 15
129
+ ```
130
+
131
+ **Default:** `21`
132
+
133
+ **Returns:** `Integer` - The ID length
134
+
135
+ #### opaque_id_alphabet
136
+
137
+ Gets or sets the alphabet for ID generation.
138
+
139
+ ```ruby
140
+ self.opaque_id_alphabet = OpaqueId::STANDARD_ALPHABET
141
+ opaque_id_alphabet
142
+ # => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
143
+ ```
144
+
145
+ **Default:** `OpaqueId::ALPHANUMERIC_ALPHABET`
146
+
147
+ **Returns:** `String` - The alphabet
148
+
149
+ #### opaque_id_require_letter_start
150
+
151
+ Gets or sets whether IDs must start with a letter.
152
+
153
+ ```ruby
154
+ self.opaque_id_require_letter_start = true
155
+ opaque_id_require_letter_start
156
+ # => true
157
+ ```
158
+
159
+ **Default:** `false`
160
+
161
+ **Returns:** `Boolean` - Whether letter start is required
162
+
163
+ #### opaque_id_max_retry
164
+
165
+ Gets or sets the maximum retry attempts for collision handling.
166
+
167
+ ```ruby
168
+ self.opaque_id_max_retry = 5
169
+ opaque_id_max_retry
170
+ # => 5
171
+ ```
172
+
173
+ **Default:** `3`
174
+
175
+ **Returns:** `Integer` - Maximum retry attempts
176
+
177
+ #### opaque_id_purge_chars
178
+
179
+ Gets or sets characters to exclude from generated IDs.
180
+
181
+ ```ruby
182
+ self.opaque_id_purge_chars = ['0', 'O', 'l', 'I']
183
+ opaque_id_purge_chars
184
+ # => ['0', 'O', 'l', 'I']
185
+ ```
186
+
187
+ **Default:** `[]`
188
+
189
+ **Returns:** `Array<String>` - Characters to exclude
190
+
191
+ #### find_by_opaque_id
192
+
193
+ Finds a record by its opaque ID.
194
+
195
+ ```ruby
196
+ User.find_by_opaque_id("V1StGXR8_Z5jdHi6B-myT")
197
+ ```
198
+
199
+ **Parameters:**
200
+
201
+ | Parameter | Type | Description |
202
+ | ----------- | ------ | --------------------------- |
203
+ | `opaque_id` | String | The opaque ID to search for |
204
+
205
+ **Returns:** `ActiveRecord::Base` or `nil` - The found record or nil
206
+
207
+ **Examples:**
208
+
209
+ ```ruby
210
+ # Find existing user
211
+ user = User.find_by_opaque_id("V1StGXR8_Z5jdHi6B-myT")
212
+ # => #<User id: 1, opaque_id: "V1StGXR8_Z5jdHi6B-myT", ...>
213
+
214
+ # Find non-existent user
215
+ user = User.find_by_opaque_id("nonexistent")
216
+ # => nil
217
+ ```
218
+
219
+ #### find_by_opaque_id!
220
+
221
+ Finds a record by its opaque ID, raising an exception if not found.
222
+
223
+ ```ruby
224
+ User.find_by_opaque_id!("V1StGXR8_Z5jdHi6B-myT")
225
+ ```
226
+
227
+ **Parameters:**
228
+
229
+ | Parameter | Type | Description |
230
+ | ----------- | ------ | --------------------------- |
231
+ | `opaque_id` | String | The opaque ID to search for |
232
+
233
+ **Returns:** `ActiveRecord::Base` - The found record
234
+
235
+ **Raises:**
236
+
237
+ - `ActiveRecord::RecordNotFound` - If no record is found
238
+
239
+ **Examples:**
240
+
241
+ ```ruby
242
+ # Find existing user
243
+ user = User.find_by_opaque_id!("V1StGXR8_Z5jdHi6B-myT")
244
+ # => #<User id: 1, opaque_id: "V1StGXR8_Z5jdHi6B-myT", ...>
245
+
246
+ # Find non-existent user
247
+ user = User.find_by_opaque_id!("nonexistent")
248
+ # => ActiveRecord::RecordNotFound: Couldn't find User with opaque_id 'nonexistent'
249
+ ```
250
+
251
+ ### Instance Methods
252
+
253
+ #### opaque_id
254
+
255
+ Gets the opaque ID for the record.
256
+
257
+ ```ruby
258
+ user.opaque_id
259
+ # => "V1StGXR8_Z5jdHi6B-myT"
260
+ ```
261
+
262
+ **Returns:** `String` - The opaque ID
263
+
264
+ ### Callbacks
265
+
266
+ #### before_create :set_opaque_id
267
+
268
+ Automatically generates an opaque ID before creating a record.
269
+
270
+ ```ruby
271
+ class User < ApplicationRecord
272
+ include OpaqueId::Model
273
+ end
274
+
275
+ user = User.new(name: "John Doe")
276
+ user.save!
277
+ puts user.opaque_id
278
+ # => "V1StGXR8_Z5jdHi6B-myT"
279
+ ```
280
+
281
+ ## Rails Generator: OpaqueId::Generators::InstallGenerator
282
+
283
+ Rails generator for setting up OpaqueId in your application.
284
+
285
+ ### Usage
286
+
287
+ ```bash
288
+ rails generate opaque_id:install ModelName [options]
289
+ ```
290
+
291
+ ### Arguments
292
+
293
+ | Argument | Type | Required | Description |
294
+ | ------------ | ------ | -------- | --------------------------- |
295
+ | `model_name` | String | Yes | Name of the model to set up |
296
+
297
+ ### Options
298
+
299
+ | Option | Type | Default | Description |
300
+ | --------------- | ------ | ----------- | ------------------------------------- |
301
+ | `--column-name` | String | `opaque_id` | Column name for storing the opaque ID |
302
+
303
+ ### Examples
304
+
305
+ ```bash
306
+ # Basic usage
307
+ rails generate opaque_id:install User
308
+
309
+ # Custom column name
310
+ rails generate opaque_id:install User --column-name=public_id
311
+
312
+ # Multiple models
313
+ rails generate opaque_id:install User
314
+ rails generate opaque_id:install Post --column-name=slug
315
+ rails generate opaque_id:install Comment
316
+ ```
317
+
318
+ ### Generated Files
319
+
320
+ #### Migration
321
+
322
+ Creates a migration to add the opaque ID column:
323
+
324
+ ```ruby
325
+ class AddOpaqueIdToUsers < ActiveRecord::Migration[8.0]
326
+ def change
327
+ add_column :users, :opaque_id, :string
328
+ add_index :users, :opaque_id, unique: true
329
+ end
330
+ end
331
+ ```
332
+
333
+ #### Model Update
334
+
335
+ Updates the model file to include the concern:
336
+
337
+ ```ruby
338
+ class User < ApplicationRecord
339
+ include OpaqueId::Model
340
+ end
341
+ ```
342
+
343
+ With custom column name:
344
+
345
+ ```ruby
346
+ class User < ApplicationRecord
347
+ include OpaqueId::Model
348
+ self.opaque_id_column = :public_id
349
+ end
350
+ ```
351
+
352
+ ## Error Classes
353
+
354
+ ### OpaqueId::Error
355
+
356
+ Base error class for all OpaqueId errors.
357
+
358
+ ```ruby
359
+ OpaqueId::Error
360
+ # => StandardError
361
+ ```
362
+
363
+ ### OpaqueId::ConfigurationError
364
+
365
+ Raised when configuration parameters are invalid.
366
+
367
+ ```ruby
368
+ OpaqueId::ConfigurationError
369
+ # => OpaqueId::Error
370
+ ```
371
+
372
+ **Examples:**
373
+
374
+ ```ruby
375
+ # Invalid size
376
+ OpaqueId.generate(size: 0)
377
+ # => OpaqueId::ConfigurationError: Size must be positive
378
+
379
+ # Empty alphabet
380
+ OpaqueId.generate(alphabet: "")
381
+ # => OpaqueId::ConfigurationError: Alphabet cannot be empty
382
+ ```
383
+
384
+ ### OpaqueId::GenerationError
385
+
386
+ Raised when ID generation fails.
387
+
388
+ ```ruby
389
+ OpaqueId::GenerationError
390
+ # => OpaqueId::Error
391
+ ```
392
+
393
+ **Examples:**
394
+
395
+ ```ruby
396
+ # Collision handling failure
397
+ class User < ApplicationRecord
398
+ include OpaqueId::Model
399
+ self.opaque_id_max_retry = 0
400
+ end
401
+
402
+ user = User.create!(name: "John Doe")
403
+ # => OpaqueId::GenerationError: Failed to generate opaque ID after 0 retries
404
+ ```
405
+
406
+ ## Configuration Examples
407
+
408
+ ### Model Configuration
409
+
410
+ ```ruby
411
+ class User < ApplicationRecord
412
+ include OpaqueId::Model
413
+
414
+ # Custom column name
415
+ self.opaque_id_column = :public_id
416
+
417
+ # Custom length
418
+ self.opaque_id_length = 15
419
+
420
+ # Custom alphabet
421
+ self.opaque_id_alphabet = OpaqueId::STANDARD_ALPHABET
422
+
423
+ # Require letter start
424
+ self.opaque_id_require_letter_start = true
425
+
426
+ # Max retry attempts
427
+ self.opaque_id_max_retry = 5
428
+
429
+ # Purge characters
430
+ self.opaque_id_purge_chars = ['0', 'O', 'l', 'I']
431
+ end
432
+ ```
433
+
434
+ ### Global Configuration
435
+
436
+ ```ruby
437
+ # config/initializers/opaque_id.rb
438
+ OpaqueId.configure do |config|
439
+ config.default_length = 15
440
+ config.default_alphabet = OpaqueId::STANDARD_ALPHABET
441
+ end
442
+ ```
443
+
444
+ ## Usage Examples
445
+
446
+ ### Basic Usage
447
+
448
+ ```ruby
449
+ # Generate standalone ID
450
+ id = OpaqueId.generate
451
+ # => "V1StGXR8_Z5jdHi6B-myT"
452
+
453
+ # Generate with custom parameters
454
+ id = OpaqueId.generate(size: 10, alphabet: "0123456789")
455
+ # => "1234567890"
456
+ ```
457
+
458
+ ### ActiveRecord Integration
459
+
460
+ ```ruby
461
+ class User < ApplicationRecord
462
+ include OpaqueId::Model
463
+ end
464
+
465
+ # Create user with automatic ID generation
466
+ user = User.create!(name: "John Doe")
467
+ puts user.opaque_id
468
+ # => "V1StGXR8_Z5jdHi6B-myT"
469
+
470
+ # Find by opaque ID
471
+ found_user = User.find_by_opaque_id(user.opaque_id)
472
+ # => #<User id: 1, opaque_id: "V1StGXR8_Z5jdHi6B-myT", ...>
473
+ ```
474
+
475
+ ### Custom Configuration
476
+
477
+ ```ruby
478
+ class Order < ApplicationRecord
479
+ include OpaqueId::Model
480
+
481
+ # Numeric order numbers
482
+ self.opaque_id_column = :order_number
483
+ self.opaque_id_length = 8
484
+ self.opaque_id_alphabet = "0123456789"
485
+ end
486
+
487
+ order = Order.create!(total: 99.99)
488
+ puts order.order_number
489
+ # => "12345678"
490
+ ```
491
+
492
+ ### Error Handling
493
+
494
+ ```ruby
495
+ begin
496
+ user = User.create!(name: "John Doe")
497
+ rescue OpaqueId::GenerationError => e
498
+ Rails.logger.error "Failed to generate opaque ID: #{e.message}"
499
+ # Handle error appropriately
500
+ end
501
+ ```
502
+
503
+ ## Performance Considerations
504
+
505
+ ### Algorithm Selection
506
+
507
+ - **64-character alphabets**: Use fast path (bitwise operations)
508
+ - **Other alphabets**: Use unbiased path (rejection sampling)
509
+ - **Single-character alphabets**: Use direct repetition
510
+
511
+ ### Memory Usage
512
+
513
+ - **Per ID**: ~50-70 bytes depending on length
514
+ - **Batch generation**: Linear scaling with count
515
+ - **Garbage collection**: Minimal impact
516
+
517
+ ### Generation Speed
518
+
519
+ - **Fast Path**: Optimized for 64-character alphabets
520
+ - **Unbiased Path**: Slightly slower but ensures uniform distribution
521
+ - **Scaling**: Linear with ID length
522
+
523
+ ## Security Considerations
524
+
525
+ ### Entropy
526
+
527
+ - **21-character alphanumeric**: 125 bits entropy
528
+ - **21-character standard**: 126 bits entropy
529
+ - **15-character hexadecimal**: 60 bits entropy
530
+
531
+ ### Collision Probability
532
+
533
+ - **1M IDs**: 2.3×10⁻¹⁵ probability
534
+ - **1B IDs**: 2.3×10⁻⁹ probability
535
+ - **1T IDs**: 2.3×10⁻³ probability
536
+
537
+ ### Attack Resistance
538
+
539
+ - **Brute Force**: Extremely long time (exponential with ID length)
540
+ - **Timing Attacks**: Constant-time operations
541
+ - **Statistical Attacks**: Uniform distribution
542
+
543
+ ## Best Practices
544
+
545
+ ### 1. Choose Appropriate Configuration
546
+
547
+ ```ruby
548
+ # High security
549
+ self.opaque_id_length = 21
550
+ self.opaque_id_alphabet = OpaqueId::ALPHANUMERIC_ALPHABET
551
+
552
+ # High performance
553
+ self.opaque_id_alphabet = OpaqueId::STANDARD_ALPHABET
554
+
555
+ # Human readable
556
+ self.opaque_id_purge_chars = ['0', 'O', 'l', 'I']
557
+ ```
558
+
559
+ ### 2. Handle Errors Gracefully
560
+
561
+ ```ruby
562
+ begin
563
+ user = User.create!(name: "John Doe")
564
+ rescue OpaqueId::GenerationError => e
565
+ # Log error and handle appropriately
566
+ Rails.logger.error "ID generation failed: #{e.message}"
567
+ end
568
+ ```
569
+
570
+ ### 3. Use Appropriate Finder Methods
571
+
572
+ ```ruby
573
+ # Optional lookup
574
+ user = User.find_by_opaque_id(params[:id])
575
+
576
+ # Required lookup
577
+ user = User.find_by_opaque_id!(params[:id])
578
+ ```
579
+
580
+ ### 4. Implement Proper Indexing
581
+
582
+ ```ruby
583
+ # Ensure database indexes
584
+ add_index :users, :opaque_id, unique: true
585
+ ```
586
+
587
+ ## Next Steps
588
+
589
+ Now that you understand the API:
590
+
591
+ 1. **Explore [Getting Started](getting-started.md)** for quick setup
592
+ 2. **Check out [Usage](usage.md)** for practical examples
593
+ 3. **Review [Configuration](configuration.md)** for advanced setup
594
+ 4. **Read [Security](security.md)** for security considerations