opaque_id 1.4.0 → 1.6.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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +116 -0
- data/README.md +62 -70
- data/docs/_config.yml +2 -0
- data/docs/algorithms.md +4 -4
- data/docs/index.md +3 -3
- data/docs/performance.md +3 -3
- data/docs/usage.md +15 -15
- data/lib/opaque_id/version.rb +1 -1
- data/lib/opaque_id.rb +4 -1
- data/release-please-config.json +2 -1
- metadata +1 -9
- data/tasks/0001-prd-opaque-id-gem.md +0 -202
- data/tasks/0002-prd-publishing-release-automation.md +0 -206
- data/tasks/0003-prd-documentation-site.md +0 -191
- data/tasks/references/opaque_gem_requirements.md +0 -482
- data/tasks/references/original_identifiable_concern_and_nanoid.md +0 -110
- data/tasks/tasks-0001-prd-opaque-id-gem.md +0 -109
- data/tasks/tasks-0002-prd-publishing-release-automation.md +0 -177
- data/tasks/tasks-0003-prd-documentation-site.md +0 -84
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 016b3d2bbd7ae3af1ba38973c0b590164d7be1a4bd60a8c0e0379cfca1c9dc0f
|
4
|
+
data.tar.gz: 2924779f8769585995732ce79da51b8a1171c0b22fd33636c6476f77815adb44
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82206f53378e08c302fbb9bd56caf94238e3b0780b6a271732d00d04f0e7e37136c1c6c941ef58fe6c6fffd154c7ce1b82c0b12b76240d23399007d34d81a0bd
|
7
|
+
data.tar.gz: b5d588f50a7a2337b42fc4e021b9aef658cf3e9f92c64fe5c21c7bf901560d5575b48c05fe72edefa77112175357562b0497c4c3d2ba73dd23be2165b6eadd87
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,122 @@ All notable changes to the OpaqueId gem will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [1.6.0](https://github.com/nyaggah/opaque_id/compare/opaque_id-v1.5.0...opaque_id/v1.6.0) (2025-10-03)
|
9
|
+
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
* add external links to sidebar using JavaScript ([cd77079](https://github.com/nyaggah/opaque_id/commit/cd770795936ca76633a18f80cba8482ec09db8e2))
|
14
|
+
* add SLUG_LIKE_ALPHABET as default for URL-safe IDs ([1e52b17](https://github.com/nyaggah/opaque_id/commit/1e52b174d70ca72b74badf399401605b307e3604))
|
15
|
+
* add Table of Contents to all documentation pages ([9472175](https://github.com/nyaggah/opaque_id/commit/94721752270211d5b7db460da61d79a8a81218b2))
|
16
|
+
* complete Table of Contents implementation ([5b037e4](https://github.com/nyaggah/opaque_id/commit/5b037e42692803204bc4d8991b6d2e0c9639a05c))
|
17
|
+
* implement dynamic copyright year ([b86cea6](https://github.com/nyaggah/opaque_id/commit/b86cea66e60939377c6b0a67c1d385c9bf4bece3))
|
18
|
+
* improve documentation site theme and navigation ([304f845](https://github.com/nyaggah/opaque_id/commit/304f845a47397ea2a15f12d2c635144b39be431b))
|
19
|
+
* improve generator API and add custom column configuration ([5c1a570](https://github.com/nyaggah/opaque_id/commit/5c1a5703dee269f6985d26096942c086787296f3))
|
20
|
+
* initial release of OpaqueId gem v0.1.0 ([3a90274](https://github.com/nyaggah/opaque_id/commit/3a9027403552f8160e3aaf413d1e99ce8c63bbe4))
|
21
|
+
|
22
|
+
|
23
|
+
### Bug Fixes
|
24
|
+
|
25
|
+
* add frozen string literal comment to docs/Gemfile ([2d653a7](https://github.com/nyaggah/opaque_id/commit/2d653a7bbc071e67e94e63ebc1602768b8d04456))
|
26
|
+
* add Release Please manifest and update workflow configuration ([f33ce6c](https://github.com/nyaggah/opaque_id/commit/f33ce6c96805b6c670cd1eedf5ebed2a46e8ffa6))
|
27
|
+
* add required permissions for Release Please job ([cdf8d2b](https://github.com/nyaggah/opaque_id/commit/cdf8d2bfc5dbea9a9ec40411a988191ba91913fa))
|
28
|
+
* configure just-the-docs theme for GitHub Pages compatibility ([c2fb96c](https://github.com/nyaggah/opaque_id/commit/c2fb96c46e99fd5287520e01c3bc2dd7bd5d4817))
|
29
|
+
* correct aux_links configuration format ([61b3fb9](https://github.com/nyaggah/opaque_id/commit/61b3fb9441ce3cfc3cf30637c277d19135f89dab))
|
30
|
+
* correct release-please tag format ([b4c31e5](https://github.com/nyaggah/opaque_id/commit/b4c31e5d0f4878878dd728e82fe9f5eb09ce5b82))
|
31
|
+
* improve publish workflow trigger reliability ([9c6e240](https://github.com/nyaggah/opaque_id/commit/9c6e2402474d8ecf8a3f754a9f2ca149a63bf1d1))
|
32
|
+
* improve statistical test reliability for CI ([c9b0d52](https://github.com/nyaggah/opaque_id/commit/c9b0d52fb4e9559aad489fcf58bdff1917d613a6))
|
33
|
+
* remove unsupported Release Please configuration parameters ([e3b0d2a](https://github.com/nyaggah/opaque_id/commit/e3b0d2ac96b905316a78992004fae83423991632))
|
34
|
+
* resolve CSS linting warnings ([d01e8c0](https://github.com/nyaggah/opaque_id/commit/d01e8c05628b7f2bd8b39a13325112168cf4e689))
|
35
|
+
* resolve dependency version conflicts and improve test robustness ([af43e13](https://github.com/nyaggah/opaque_id/commit/af43e13393a452f11ce32bafb26a267d1460736c))
|
36
|
+
* resolve TOC rendering and improve workflow configuration ([244d532](https://github.com/nyaggah/opaque_id/commit/244d5321e5b18091d8599bd772a28f5a30239c80))
|
37
|
+
* update deprecated platform specifications in docs/Gemfile ([cf96134](https://github.com/nyaggah/opaque_id/commit/cf9613499150dd7152bff38aa8f8fe7ab9923eb5))
|
38
|
+
* use correct aux_links format ([d4a4bc0](https://github.com/nyaggah/opaque_id/commit/d4a4bc0c11bc66b661034915b471ed762611d821))
|
39
|
+
|
40
|
+
|
41
|
+
### Documentation
|
42
|
+
|
43
|
+
* final cleanup of over-inflated claims ([795b5b0](https://github.com/nyaggah/opaque_id/commit/795b5b0071a098f718a40e84e81ceba4525b8d3b))
|
44
|
+
* implement comprehensive documentation site with dark theme ([19cc9e3](https://github.com/nyaggah/opaque_id/commit/19cc9e30c584658559c5404a4518e871039cc223))
|
45
|
+
* implement comprehensive documentation site with dark theme ([a249c6a](https://github.com/nyaggah/opaque_id/commit/a249c6ad2253439f0d070620a914ff87597cf7cb))
|
46
|
+
* tone down over-inflated claims and remove unsubstantiated benchmarks ([6536b87](https://github.com/nyaggah/opaque_id/commit/6536b87f9a9c2049497328319641ff21368c6032))
|
47
|
+
* tone down pretentious language in algorithms intro ([58c0caf](https://github.com/nyaggah/opaque_id/commit/58c0caf116228401b6218ec589a35b350d068b49))
|
48
|
+
|
49
|
+
|
50
|
+
### Styles
|
51
|
+
|
52
|
+
* improve code formatting in generator ([ce52021](https://github.com/nyaggah/opaque_id/commit/ce52021cd60e3594dc1ee34e5b96d48568008b5a))
|
53
|
+
|
54
|
+
|
55
|
+
### Miscellaneous Chores
|
56
|
+
|
57
|
+
* configure just-the-docs theme ([3dfcac8](https://github.com/nyaggah/opaque_id/commit/3dfcac88099e65e61931e558e9242050bdbf0bc9))
|
58
|
+
* **main:** release 1.0.0 ([43282c5](https://github.com/nyaggah/opaque_id/commit/43282c5865aae3f136c9eaa49f013066a2359826))
|
59
|
+
* **main:** release 1.0.0 ([b8271ad](https://github.com/nyaggah/opaque_id/commit/b8271ad43cf6fab4687276258f0105c06a87bff7))
|
60
|
+
* **main:** release 1.0.1 ([4ee2e2c](https://github.com/nyaggah/opaque_id/commit/4ee2e2c69905e71d6c5048617af09b65cbb9e12a))
|
61
|
+
* **main:** release 1.0.1 ([e6cd2f8](https://github.com/nyaggah/opaque_id/commit/e6cd2f8f8ff545ad3010598488f71956e36a88c5))
|
62
|
+
* **main:** release 1.0.2 ([d5c7423](https://github.com/nyaggah/opaque_id/commit/d5c7423cf06ae7638d95cdd29b2631049efed727))
|
63
|
+
* **main:** release 1.0.2 ([365dff8](https://github.com/nyaggah/opaque_id/commit/365dff87a044aa967906866d6ace8d1ccad08c78))
|
64
|
+
* **main:** release opaque_id 1.1.0 ([df65c79](https://github.com/nyaggah/opaque_id/commit/df65c79d0efaee1dcd89b8af2f1b1c712c3cc2cd))
|
65
|
+
* **main:** release opaque_id 1.1.0 ([e2a4ee0](https://github.com/nyaggah/opaque_id/commit/e2a4ee0fd2d31132bc5bc0de6b6115f3ceae8afb))
|
66
|
+
* **main:** release opaque_id 1.2.0 ([1987b07](https://github.com/nyaggah/opaque_id/commit/1987b07b3db92216f3fbbe807c3ad66ca105866e))
|
67
|
+
* **main:** release opaque_id 1.2.0 ([aacd20a](https://github.com/nyaggah/opaque_id/commit/aacd20aa5eaa1fe2fa76d89cc6bdab26214b744d))
|
68
|
+
* **main:** release opaque_id 1.3.0 ([964b1b4](https://github.com/nyaggah/opaque_id/commit/964b1b421b1fac3fe43b15549e0e6b216b600357))
|
69
|
+
* **main:** release opaque_id 1.3.0 ([76453ad](https://github.com/nyaggah/opaque_id/commit/76453ad18b2d2b81548e49dafb5dde6fb52abcca))
|
70
|
+
* **main:** release opaque_id 1.4.0 ([6c3ccd1](https://github.com/nyaggah/opaque_id/commit/6c3ccd108b3eea0bbf0ac71fda4ccc0e47e97b95))
|
71
|
+
* **main:** release opaque_id 1.4.0 ([3d9d818](https://github.com/nyaggah/opaque_id/commit/3d9d818b34771ad1ca729f3f9b443784a6965568))
|
72
|
+
* **main:** release opaque_id 1.5.0 ([113af74](https://github.com/nyaggah/opaque_id/commit/113af74dda07330bf7108977147362385dc806ae))
|
73
|
+
* **main:** release opaque_id 1.5.0 ([ff14fe9](https://github.com/nyaggah/opaque_id/commit/ff14fe92d94cf4ecbe5aaeb31ad6cd044bfa89d0))
|
74
|
+
* remove tasks/ directory from source control ([e007ead](https://github.com/nyaggah/opaque_id/commit/e007ead90fa3fbb62ab17e334563e69899afb259))
|
75
|
+
* sync version to 1.0.2 ([2f870c8](https://github.com/nyaggah/opaque_id/commit/2f870c89d486f0a5cda8ef37e5544c29c8fa2919))
|
76
|
+
* update author name and GitHub URLs ([e911548](https://github.com/nyaggah/opaque_id/commit/e911548e89e1e60ac3281157059f7c7b812750bd))
|
77
|
+
* update bundle and add Linux platform support ([661842f](https://github.com/nyaggah/opaque_id/commit/661842f0ba66ce36962bb11f75a1b0e35eb5a3e7))
|
78
|
+
* update Gemfile.lock after dependency check ([058281f](https://github.com/nyaggah/opaque_id/commit/058281f6e07f3c9a1057c6fbeb0200bccf221558))
|
79
|
+
|
80
|
+
|
81
|
+
### Code Refactoring
|
82
|
+
|
83
|
+
* improve generator code quality and resolve RuboCop issues ([b658bbc](https://github.com/nyaggah/opaque_id/commit/b658bbcb29f6c0134f2bf256b24c5b3c2bccb265))
|
84
|
+
* integrate Release Please into main CI workflow ([8a0b189](https://github.com/nyaggah/opaque_id/commit/8a0b189a8bfbfcc07275c9bdda3397ff367b2054))
|
85
|
+
* simplify external links implementation ([5cb84c1](https://github.com/nyaggah/opaque_id/commit/5cb84c1e8952a0fd1aa4b9089b9ff11829dc397c))
|
86
|
+
|
87
|
+
|
88
|
+
### Tests
|
89
|
+
|
90
|
+
* add test for lowercase model names ([8ef4675](https://github.com/nyaggah/opaque_id/commit/8ef4675a0698d2cda054084360c9e80d956ddb14))
|
91
|
+
* update tests for SLUG_LIKE_ALPHABET defaults ([d7296c7](https://github.com/nyaggah/opaque_id/commit/d7296c77b1b7a995e7a42b3ba546fdeb8e41c297))
|
92
|
+
* update tests for SLUG_LIKE_ALPHABET defaults ([d1f8f26](https://github.com/nyaggah/opaque_id/commit/d1f8f26e7be80f6cbefc98563c0ffa45033372dc))
|
93
|
+
|
94
|
+
## [1.5.0](https://github.com/nyaggah/opaque_id/compare/opaque_id/v1.4.0...opaque_id/v1.5.0) (2025-10-03)
|
95
|
+
|
96
|
+
|
97
|
+
### Features
|
98
|
+
|
99
|
+
* add SLUG_LIKE_ALPHABET as default for URL-safe IDs ([1e52b17](https://github.com/nyaggah/opaque_id/commit/1e52b174d70ca72b74badf399401605b307e3604))
|
100
|
+
|
101
|
+
|
102
|
+
### Bug Fixes
|
103
|
+
|
104
|
+
* resolve TOC rendering and improve workflow configuration ([244d532](https://github.com/nyaggah/opaque_id/commit/244d5321e5b18091d8599bd772a28f5a30239c80))
|
105
|
+
|
106
|
+
|
107
|
+
### Documentation
|
108
|
+
|
109
|
+
* final cleanup of over-inflated claims ([795b5b0](https://github.com/nyaggah/opaque_id/commit/795b5b0071a098f718a40e84e81ceba4525b8d3b))
|
110
|
+
* tone down over-inflated claims and remove unsubstantiated benchmarks ([6536b87](https://github.com/nyaggah/opaque_id/commit/6536b87f9a9c2049497328319641ff21368c6032))
|
111
|
+
* tone down pretentious language in algorithms intro ([58c0caf](https://github.com/nyaggah/opaque_id/commit/58c0caf116228401b6218ec589a35b350d068b49))
|
112
|
+
|
113
|
+
|
114
|
+
### Miscellaneous Chores
|
115
|
+
|
116
|
+
* remove tasks/ directory from source control ([e007ead](https://github.com/nyaggah/opaque_id/commit/e007ead90fa3fbb62ab17e334563e69899afb259))
|
117
|
+
|
118
|
+
|
119
|
+
### Tests
|
120
|
+
|
121
|
+
* update tests for SLUG_LIKE_ALPHABET defaults ([d7296c7](https://github.com/nyaggah/opaque_id/commit/d7296c77b1b7a995e7a42b3ba546fdeb8e41c297))
|
122
|
+
* update tests for SLUG_LIKE_ALPHABET defaults ([d1f8f26](https://github.com/nyaggah/opaque_id/commit/d1f8f26e7be80f6cbefc98563c0ffa45033372dc))
|
123
|
+
|
8
124
|
## [1.4.0](https://github.com/nyaggah/opaque_id/compare/opaque_id/v1.3.0...opaque_id/v1.4.0) (2025-10-02)
|
9
125
|
|
10
126
|
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
[](https://github.com/rubocop/rubocop)
|
5
5
|
[](https://rubygems.org/gems/opaque_id)
|
6
6
|
|
7
|
-
A Ruby gem for generating
|
7
|
+
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.
|
8
8
|
|
9
9
|
## Table of Contents
|
10
10
|
|
@@ -87,12 +87,12 @@ A Ruby gem for generating cryptographically secure, collision-free opaque IDs fo
|
|
87
87
|
## Features
|
88
88
|
|
89
89
|
- **🔐 Cryptographically Secure**: Uses Ruby's `SecureRandom` for secure ID generation
|
90
|
-
- **⚡
|
91
|
-
- **🎯 Collision
|
90
|
+
- **⚡ Performance Optimized**: Efficient algorithms with fast paths for 64-character alphabets
|
91
|
+
- **🎯 Collision Resistant**: Built-in collision detection with configurable retry attempts
|
92
92
|
- **🔧 Highly Configurable**: Customizable alphabet, length, column name, and validation rules
|
93
|
-
- **🚀 Rails Integration**:
|
93
|
+
- **🚀 Rails Integration**: ActiveRecord integration with automatic ID generation
|
94
94
|
- **📦 Rails Generator**: One-command setup with `rails generate opaque_id:install`
|
95
|
-
- **🧪
|
95
|
+
- **🧪 Tested**: Includes test suite with statistical uniformity tests
|
96
96
|
- **📚 Rails 8.0+ Compatible**: Built for modern Rails applications
|
97
97
|
|
98
98
|
## Installation
|
@@ -176,15 +176,34 @@ end
|
|
176
176
|
|
177
177
|
# IDs are automatically generated on creation
|
178
178
|
user = User.create!(name: "John Doe")
|
179
|
-
puts user.opaque_id # => "
|
179
|
+
puts user.opaque_id # => "izkpm55j334u8x9y2"
|
180
180
|
|
181
181
|
# Find by opaque ID
|
182
|
-
user = User.find_by_opaque_id("
|
183
|
-
user = User.find_by_opaque_id!("
|
182
|
+
user = User.find_by_opaque_id("izkpm55j334u8x9y2")
|
183
|
+
user = User.find_by_opaque_id!("izkpm55j334u8x9y2") # raises if not found
|
184
184
|
```
|
185
185
|
|
186
186
|
## Usage
|
187
187
|
|
188
|
+
### URL-Safe, Double-Click Selectable IDs
|
189
|
+
|
190
|
+
OpaqueId defaults to generating **slug-like IDs** that are perfect for URLs and user-facing identifiers:
|
191
|
+
|
192
|
+
- **URL-safe**: No special characters that need encoding
|
193
|
+
- **Double-click selectable**: Users can easily select the entire ID
|
194
|
+
- **Shorter than UUIDs**: 18 characters vs 36 for UUIDs
|
195
|
+
- **Collision resistant**: Built on Ruby's `SecureRandom` for security
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
# Default generation creates slug-like IDs
|
199
|
+
id = OpaqueId.generate
|
200
|
+
# => "izkpm55j334u8x9y2" # Perfect for URLs and user selection
|
201
|
+
|
202
|
+
# Compare to UUIDs
|
203
|
+
uuid = SecureRandom.uuid
|
204
|
+
# => "7cb776c5-8c12-4b1a-84aa-9941b815d873" # Harder to select, longer
|
205
|
+
```
|
206
|
+
|
188
207
|
### Standalone ID Generation
|
189
208
|
|
190
209
|
OpaqueId can be used independently of ActiveRecord for generating secure IDs in any Ruby application:
|
@@ -192,13 +211,13 @@ OpaqueId can be used independently of ActiveRecord for generating secure IDs in
|
|
192
211
|
#### Basic Usage
|
193
212
|
|
194
213
|
```ruby
|
195
|
-
# Generate with default settings (
|
214
|
+
# Generate with default settings (18 characters, slug-like)
|
196
215
|
id = OpaqueId.generate
|
197
|
-
# => "
|
216
|
+
# => "izkpm55j334u8x9y2"
|
198
217
|
|
199
218
|
# Custom length
|
200
219
|
id = OpaqueId.generate(size: 10)
|
201
|
-
# => "
|
220
|
+
# => "izkpm55j334u"
|
202
221
|
|
203
222
|
# Custom alphabet
|
204
223
|
id = OpaqueId.generate(alphabet: OpaqueId::STANDARD_ALPHABET)
|
@@ -210,7 +229,7 @@ id = OpaqueId.generate(size: 8, alphabet: "ABCDEFGH")
|
|
210
229
|
|
211
230
|
# Generate multiple IDs
|
212
231
|
ids = 5.times.map { OpaqueId.generate(size: 8) }
|
213
|
-
# => ["
|
232
|
+
# => ["izkpm55j", "334u8x9y", "2abc1234", "def5678g", "hij9klmn"]
|
214
233
|
```
|
215
234
|
|
216
235
|
#### Standalone Use Cases
|
@@ -229,7 +248,7 @@ class BackgroundJob
|
|
229
248
|
end
|
230
249
|
|
231
250
|
job_id = BackgroundJob.enqueue(ProcessDataJob, user_id: 123)
|
232
|
-
# => "
|
251
|
+
# => "izkpm55j334u8x9y2"
|
233
252
|
```
|
234
253
|
|
235
254
|
##### Temporary File Names
|
@@ -243,7 +262,7 @@ def create_temp_file(content)
|
|
243
262
|
end
|
244
263
|
|
245
264
|
filename = create_temp_file("Hello World")
|
246
|
-
# => "
|
265
|
+
# => "temp_izkpm55j334u8x9y2.txt"
|
247
266
|
```
|
248
267
|
|
249
268
|
##### Cache Keys
|
@@ -264,7 +283,7 @@ user_key = CacheManager.user_cache_key(123)
|
|
264
283
|
# => "user:V1StGX:123"
|
265
284
|
|
266
285
|
session_key = CacheManager.session_cache_key
|
267
|
-
# => "session:
|
286
|
+
# => "session:izkpm55j334u8x9y2"
|
268
287
|
```
|
269
288
|
|
270
289
|
##### Webhook Signatures
|
@@ -281,7 +300,7 @@ class WebhookService
|
|
281
300
|
end
|
282
301
|
|
283
302
|
signature = WebhookService.generate_signature({ user_id: 123 })
|
284
|
-
# => "1703123456:
|
303
|
+
# => "1703123456:izkpm55j334u8x9y2:1234567890"
|
285
304
|
```
|
286
305
|
|
287
306
|
##### Database Migration IDs
|
@@ -309,7 +328,7 @@ class EmailService
|
|
309
328
|
end
|
310
329
|
|
311
330
|
tracking_id = EmailService.tracking_pixel_id
|
312
|
-
# => "
|
331
|
+
# => "izkpm55j334u8x9y2abc"
|
313
332
|
|
314
333
|
# Use in email template
|
315
334
|
# <img src="https://example.com/track/#{tracking_id}" width="1" height="1" />
|
@@ -328,7 +347,7 @@ class ApiLogger
|
|
328
347
|
end
|
329
348
|
|
330
349
|
request_id = ApiLogger.log_request("/api/users", { page: 1 })
|
331
|
-
# => "
|
350
|
+
# => "izkpm55j334u8x9y2"
|
332
351
|
```
|
333
352
|
|
334
353
|
##### Batch Processing IDs
|
@@ -350,9 +369,9 @@ class BatchProcessor
|
|
350
369
|
end
|
351
370
|
|
352
371
|
batch_id = BatchProcessor.process_batch([1, 2, 3, 4, 5])
|
353
|
-
# => "
|
354
|
-
# => Processing item
|
355
|
-
# => Processing item
|
372
|
+
# => "izkpm55j334u8x9y2"
|
373
|
+
# => Processing item izkpm55j334u8x9y2_000: 1
|
374
|
+
# => Processing item izkpm55j334u8x9y2_001: 2
|
356
375
|
# => ...
|
357
376
|
```
|
358
377
|
|
@@ -417,7 +436,7 @@ end
|
|
417
436
|
|
418
437
|
# Create a new post - opaque_id is automatically generated
|
419
438
|
post = Post.create!(title: "Hello World", content: "This is my first post")
|
420
|
-
puts post.opaque_id # => "
|
439
|
+
puts post.opaque_id # => "izkpm55j334u8x9y2"
|
421
440
|
|
422
441
|
# Create multiple posts
|
423
442
|
posts = Post.create!([
|
@@ -427,9 +446,9 @@ posts = Post.create!([
|
|
427
446
|
])
|
428
447
|
|
429
448
|
posts.each { |p| puts "#{p.title}: #{p.opaque_id}" }
|
430
|
-
# => Post 1:
|
431
|
-
# => Post 2:
|
432
|
-
# => Post 3:
|
449
|
+
# => Post 1: izkpm55j334u8x9y2
|
450
|
+
# => Post 2: 334u8x9y2abc1234
|
451
|
+
# => Post 3: abc1234def5678gh
|
433
452
|
```
|
434
453
|
|
435
454
|
#### Custom Configuration
|
@@ -482,7 +501,7 @@ class ApiKey < ApplicationRecord
|
|
482
501
|
self.opaque_id_max_retry = 10
|
483
502
|
end
|
484
503
|
|
485
|
-
# Generated API keys will look like: "
|
504
|
+
# Generated API keys will look like: "izkpm55j334u8x9y2abc1234def5678gh"
|
486
505
|
```
|
487
506
|
|
488
507
|
##### Short URL Configuration
|
@@ -529,7 +548,7 @@ class Upload < ApplicationRecord
|
|
529
548
|
self.opaque_id_purge_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|']
|
530
549
|
end
|
531
550
|
|
532
|
-
# Generated filenames will look like: "
|
551
|
+
# Generated filenames will look like: "izkpm55j334u8x9y2"
|
533
552
|
```
|
534
553
|
|
535
554
|
##### Session Token Configuration
|
@@ -554,7 +573,7 @@ class Session < ApplicationRecord
|
|
554
573
|
self.opaque_id_max_retry = 8
|
555
574
|
end
|
556
575
|
|
557
|
-
# Generated session tokens will look like: "
|
576
|
+
# Generated session tokens will look like: "izkpm55j334u8x9y2abc123"
|
558
577
|
```
|
559
578
|
|
560
579
|
##### Custom Alphabet Examples
|
@@ -592,7 +611,7 @@ end
|
|
592
611
|
|
593
612
|
```ruby
|
594
613
|
# Find by opaque ID (returns nil if not found)
|
595
|
-
user = User.find_by_opaque_id("
|
614
|
+
user = User.find_by_opaque_id("izkpm55j334u8x9y2")
|
596
615
|
if user
|
597
616
|
puts "Found user: #{user.name}"
|
598
617
|
else
|
@@ -600,14 +619,14 @@ else
|
|
600
619
|
end
|
601
620
|
|
602
621
|
# Find by opaque ID (raises ActiveRecord::RecordNotFound if not found)
|
603
|
-
user = User.find_by_opaque_id!("
|
622
|
+
user = User.find_by_opaque_id!("izkpm55j334u8x9y2")
|
604
623
|
puts "Found user: #{user.name}"
|
605
624
|
|
606
625
|
# Use in controllers for public-facing URLs
|
607
626
|
class PostsController < ApplicationController
|
608
627
|
def show
|
609
628
|
@post = Post.find_by_opaque_id!(params[:id])
|
610
|
-
# This allows URLs like /posts/
|
629
|
+
# This allows URLs like /posts/izkpm55j334u8x9y2
|
611
630
|
end
|
612
631
|
end
|
613
632
|
|
@@ -672,8 +691,8 @@ OpaqueId provides comprehensive configuration options to customize ID generation
|
|
672
691
|
- **Performance**: Longer IDs are more secure but use more storage
|
673
692
|
- **Examples**:
|
674
693
|
- `6` → Short URLs: `"V1StGX"`
|
675
|
-
- `
|
676
|
-
- `32` → API Keys: `"
|
694
|
+
- `18` → Default: `"izkpm55j334u8x9y2"`
|
695
|
+
- `32` → API Keys: `"izkpm55j334u8x9y2abc1234def5678gh"`
|
677
696
|
|
678
697
|
#### `opaque_id_alphabet`
|
679
698
|
|
@@ -691,7 +710,7 @@ OpaqueId provides comprehensive configuration options to customize ID generation
|
|
691
710
|
- **Purpose**: Ensures IDs start with a letter for better readability
|
692
711
|
- **Use Cases**: When IDs are user-facing or need to be easily readable
|
693
712
|
- **Performance**: Slight overhead due to rejection sampling
|
694
|
-
- **Example**: `true` → `"
|
713
|
+
- **Example**: `true` → `"izkpm55j334u8x9y2"`, `false` → `"zkpm55j334u8x9y2"`
|
695
714
|
|
696
715
|
#### `opaque_id_purge_chars`
|
697
716
|
|
@@ -744,7 +763,7 @@ OpaqueId::ALPHANUMERIC_ALPHABET
|
|
744
763
|
|
745
764
|
```ruby
|
746
765
|
OpaqueId.generate(size: 8, alphabet: OpaqueId::ALPHANUMERIC_ALPHABET)
|
747
|
-
# => "
|
766
|
+
# => "izkpm55j"
|
748
767
|
```
|
749
768
|
|
750
769
|
### `STANDARD_ALPHABET`
|
@@ -932,41 +951,14 @@ def generate(size:, alphabet:)
|
|
932
951
|
end
|
933
952
|
```
|
934
953
|
|
935
|
-
## Performance
|
936
|
-
|
937
|
-
### Generation Speed (IDs per second)
|
938
|
-
|
939
|
-
| Alphabet Size | Algorithm | Performance | Relative Speed |
|
940
|
-
| ------------- | --------- | ------------------ | --------------- |
|
941
|
-
| 64 characters | Fast Path | ~2,500,000 IDs/sec | 100% (baseline) |
|
942
|
-
| 62 characters | Unbiased | ~1,200,000 IDs/sec | 48% |
|
943
|
-
| 36 characters | Unbiased | ~1,100,000 IDs/sec | 44% |
|
944
|
-
| 26 characters | Unbiased | ~1,000,000 IDs/sec | 40% |
|
945
|
-
| 10 characters | Unbiased | ~900,000 IDs/sec | 36% |
|
946
|
-
|
947
|
-
_Benchmarks run on Ruby 3.2.0, generating 21-character IDs_
|
948
|
-
|
949
|
-
### Memory Usage
|
950
|
-
|
951
|
-
| Algorithm | Memory per ID | Memory per 1M IDs |
|
952
|
-
| --------- | ------------- | ----------------- |
|
953
|
-
| Fast Path | ~21 bytes | ~21 MB |
|
954
|
-
| Unbiased | ~21 bytes | ~21 MB |
|
955
|
-
|
956
|
-
_Memory usage is consistent regardless of algorithm choice_
|
957
|
-
|
958
|
-
### Collision Probability
|
959
|
-
|
960
|
-
For 21-character IDs with different alphabets:
|
954
|
+
## Performance Characteristics
|
961
955
|
|
962
|
-
|
963
|
-
| --------------------- | ---------- | ---------------------------- |
|
964
|
-
| STANDARD_ALPHABET | 64 | 2.9 × 10^37 |
|
965
|
-
| ALPHANUMERIC_ALPHABET | 62 | 1.4 × 10^37 |
|
966
|
-
| Numeric (0-9) | 10 | 1.0 × 10^21 |
|
967
|
-
| Binary (0-1) | 2 | 2.1 × 10^6 |
|
956
|
+
OpaqueId is designed for efficient ID generation with different performance characteristics based on alphabet size:
|
968
957
|
|
969
|
-
|
958
|
+
- **64-character alphabets**: Use optimized bitwise operations for faster generation
|
959
|
+
- **Other alphabets**: Use rejection sampling for unbiased distribution with slight overhead
|
960
|
+
- **Memory usage**: Scales linearly with ID length
|
961
|
+
- **Collision resistance**: Extremely low probability for typical use cases
|
970
962
|
|
971
963
|
### Performance Characteristics
|
972
964
|
|
@@ -975,7 +967,7 @@ _Collision probability calculated using birthday paradox formula_
|
|
975
967
|
- **Time Complexity**: O(n) where n = ID length
|
976
968
|
- **Space Complexity**: O(n)
|
977
969
|
- **Rejection Rate**: 0% (no rejections)
|
978
|
-
- **Distribution**:
|
970
|
+
- **Distribution**: Uniform distribution
|
979
971
|
- **Best For**: High-performance applications, short URLs
|
980
972
|
|
981
973
|
#### Unbiased Path (other alphabets)
|
@@ -983,7 +975,7 @@ _Collision probability calculated using birthday paradox formula_
|
|
983
975
|
- **Time Complexity**: O(n × (1 + rejection_rate)) where rejection_rate ≈ 0.01
|
984
976
|
- **Space Complexity**: O(n)
|
985
977
|
- **Rejection Rate**: <1% for most alphabet sizes
|
986
|
-
- **Distribution**:
|
978
|
+
- **Distribution**: Uniform distribution using rejection sampling
|
987
979
|
- **Best For**: General-purpose applications, custom alphabets
|
988
980
|
|
989
981
|
### Real-World Performance
|
data/docs/_config.yml
CHANGED
data/docs/algorithms.md
CHANGED
@@ -8,7 +8,7 @@ permalink: /algorithms/
|
|
8
8
|
|
9
9
|
# Algorithms
|
10
10
|
|
11
|
-
OpaqueId
|
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
|
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
|
-
- **
|
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
|
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
|
|
data/docs/index.md
CHANGED
@@ -12,7 +12,7 @@ permalink: /
|
|
12
12
|
[](https://github.com/rubocop/rubocop)
|
13
13
|
[](https://rubygems.org/gems/opaque_id)
|
14
14
|
|
15
|
-
A Ruby gem for generating
|
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 # => "
|
60
|
+
puts user.opaque_id # => "izkpm55j334u8x9y2"
|
61
61
|
|
62
62
|
# Find by opaque_id
|
63
|
-
user = User.find_by_opaque_id("
|
63
|
+
user = User.find_by_opaque_id("izkpm55j334u8x9y2")
|
64
64
|
```
|
65
65
|
|
66
66
|
## Why OpaqueId?
|
data/docs/performance.md
CHANGED
@@ -8,7 +8,7 @@ permalink: /performance/
|
|
8
8
|
|
9
9
|
# Performance
|
10
10
|
|
11
|
-
OpaqueId is designed for
|
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
|
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
|
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
|
|
data/docs/usage.md
CHANGED
@@ -20,9 +20,9 @@ OpaqueId can be used independently of ActiveRecord for generating secure, random
|
|
20
20
|
### Basic Usage
|
21
21
|
|
22
22
|
```ruby
|
23
|
-
# Generate a default opaque ID (
|
23
|
+
# Generate a default opaque ID (18 characters, slug-like)
|
24
24
|
id = OpaqueId.generate
|
25
|
-
# => "
|
25
|
+
# => "izkpm55j334u8x9y2"
|
26
26
|
|
27
27
|
# Generate multiple IDs
|
28
28
|
ids = 5.times.map { OpaqueId.generate }
|
@@ -34,15 +34,15 @@ ids = 5.times.map { OpaqueId.generate }
|
|
34
34
|
```ruby
|
35
35
|
# Custom length
|
36
36
|
id = OpaqueId.generate(size: 10)
|
37
|
-
# => "
|
37
|
+
# => "izkpm55j334u"
|
38
38
|
|
39
39
|
# Custom alphabet
|
40
40
|
id = OpaqueId.generate(alphabet: OpaqueId::STANDARD_ALPHABET)
|
41
|
-
# => "
|
41
|
+
# => "izkpm55j334u8x9y2"
|
42
42
|
|
43
43
|
# Both custom length and alphabet
|
44
44
|
id = OpaqueId.generate(size: 15, alphabet: OpaqueId::STANDARD_ALPHABET)
|
45
|
-
# => "
|
45
|
+
# => "izkpm55j334u8x9y"
|
46
46
|
```
|
47
47
|
|
48
48
|
### Built-in Alphabets
|
@@ -50,11 +50,11 @@ id = OpaqueId.generate(size: 15, alphabet: OpaqueId::STANDARD_ALPHABET)
|
|
50
50
|
```ruby
|
51
51
|
# Alphanumeric alphabet (default) - A-Z, a-z, 0-9
|
52
52
|
id = OpaqueId.generate(alphabet: OpaqueId::ALPHANUMERIC_ALPHABET)
|
53
|
-
# => "
|
53
|
+
# => "izkpm55j334u8x9y2"
|
54
54
|
|
55
55
|
# Standard alphabet - A-Z, a-z, 0-9, -, _
|
56
56
|
id = OpaqueId.generate(alphabet: OpaqueId::STANDARD_ALPHABET)
|
57
|
-
# => "
|
57
|
+
# => "izkpm55j334u8x9y2"
|
58
58
|
```
|
59
59
|
|
60
60
|
### Custom Alphabets
|
@@ -73,7 +73,7 @@ id = OpaqueId.generate(size: 16, alphabet: hex_alphabet)
|
|
73
73
|
# URL-safe characters
|
74
74
|
url_safe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
75
75
|
id = OpaqueId.generate(size: 12, alphabet: url_safe)
|
76
|
-
# => "
|
76
|
+
# => "izkpm55j334u8x"
|
77
77
|
```
|
78
78
|
|
79
79
|
## ActiveRecord Integration
|
@@ -94,7 +94,7 @@ end
|
|
94
94
|
# Create a new user - opaque_id is automatically generated
|
95
95
|
user = User.create!(name: "John Doe", email: "john@example.com")
|
96
96
|
puts user.opaque_id
|
97
|
-
# => "
|
97
|
+
# => "izkpm55j334u8x9y2"
|
98
98
|
|
99
99
|
# The ID is generated before the record is saved
|
100
100
|
user = User.new(name: "Jane Smith")
|
@@ -110,10 +110,10 @@ puts user.opaque_id
|
|
110
110
|
|
111
111
|
```ruby
|
112
112
|
# Find by opaque_id (returns nil if not found)
|
113
|
-
user = User.find_by_opaque_id("
|
113
|
+
user = User.find_by_opaque_id("izkpm55j334u8x9y2")
|
114
114
|
|
115
115
|
# Find by opaque_id (raises exception if not found)
|
116
|
-
user = User.find_by_opaque_id!("
|
116
|
+
user = User.find_by_opaque_id!("izkpm55j334u8x9y2")
|
117
117
|
# => ActiveRecord::RecordNotFound if not found
|
118
118
|
|
119
119
|
# Use in scopes
|
@@ -123,7 +123,7 @@ class User < ApplicationRecord
|
|
123
123
|
scope :by_opaque_id, ->(id) { where(opaque_id: id) }
|
124
124
|
end
|
125
125
|
|
126
|
-
users = User.by_opaque_id("
|
126
|
+
users = User.by_opaque_id("izkpm55j334u8x9y2")
|
127
127
|
```
|
128
128
|
|
129
129
|
### Custom Column Names
|
@@ -139,7 +139,7 @@ end
|
|
139
139
|
# Now the methods use the custom column name
|
140
140
|
user = User.create!(name: "John Doe")
|
141
141
|
puts user.public_id
|
142
|
-
# => "
|
142
|
+
# => "izkpm55j334u8x9y2"
|
143
143
|
|
144
144
|
user = User.find_by_public_id("V1StGXR8_Z5jdHi6B-myT")
|
145
145
|
```
|
@@ -198,7 +198,7 @@ end
|
|
198
198
|
# Create an order
|
199
199
|
order = Order.create!(user_id: 1, total: 99.99)
|
200
200
|
puts order.opaque_id
|
201
|
-
# => "
|
201
|
+
# => "izkpm55j334u8x"
|
202
202
|
|
203
203
|
# Use in URLs
|
204
204
|
order_url(order.opaque_id)
|
@@ -259,7 +259,7 @@ end
|
|
259
259
|
# Create a user
|
260
260
|
user = User.create!(name: "John Doe", email: "john@example.com")
|
261
261
|
puts user.opaque_id
|
262
|
-
# => "
|
262
|
+
# => "izkpm55j334u8x9y2" (starts with letter)
|
263
263
|
|
264
264
|
# Use in user profiles
|
265
265
|
user_url(user.opaque_id)
|