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