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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +206 -0
- data/README.md +93 -79
- data/docs/_config.yml +2 -0
- data/docs/algorithms.md +4 -4
- data/docs/api-reference.md +6 -6
- data/docs/benchmarks.md +385 -0
- data/docs/configuration.md +32 -18
- data/docs/index.md +21 -3
- data/docs/performance.md +3 -3
- data/docs/usage.md +22 -18
- data/lib/generators/opaque_id/install_generator.rb +13 -0
- data/lib/generators/opaque_id/templates/migration.rb.tt +3 -3
- data/lib/opaque_id/model.rb +2 -2
- data/lib/opaque_id/version.rb +1 -1
- data/lib/opaque_id.rb +4 -1
- data/release-please-config.json +2 -1
- metadata +2 -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
data/docs/usage.md
CHANGED
@@ -20,13 +20,13 @@ 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
|
+
# => "izkpm55j334u8x9y2a"
|
26
26
|
|
27
27
|
# Generate multiple IDs
|
28
28
|
ids = 5.times.map { OpaqueId.generate }
|
29
|
-
# => ["
|
29
|
+
# => ["izkpm55j334u8x9y2a", "k8jh2mn9pl3qr7st1v", ...]
|
30
30
|
```
|
31
31
|
|
32
32
|
### Custom Parameters
|
@@ -34,7 +34,7 @@ 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)
|
@@ -42,15 +42,19 @@ id = OpaqueId.generate(alphabet: OpaqueId::STANDARD_ALPHABET)
|
|
42
42
|
|
43
43
|
# Both custom length and alphabet
|
44
44
|
id = OpaqueId.generate(size: 15, alphabet: OpaqueId::STANDARD_ALPHABET)
|
45
|
-
# => "
|
45
|
+
# => "V1StGXR8_Z5jdHi6B"
|
46
46
|
```
|
47
47
|
|
48
48
|
### Built-in Alphabets
|
49
49
|
|
50
50
|
```ruby
|
51
|
-
#
|
51
|
+
# Slug-like alphabet (default) - 0-9, a-z
|
52
|
+
id = OpaqueId.generate(alphabet: OpaqueId::SLUG_LIKE_ALPHABET)
|
53
|
+
# => "izkpm55j334u8x9y2a"
|
54
|
+
|
55
|
+
# Alphanumeric alphabet - A-Z, a-z, 0-9
|
52
56
|
id = OpaqueId.generate(alphabet: OpaqueId::ALPHANUMERIC_ALPHABET)
|
53
|
-
# => "
|
57
|
+
# => "V1StGXR8Z5jdHi6BmyT"
|
54
58
|
|
55
59
|
# Standard alphabet - A-Z, a-z, 0-9, -, _
|
56
60
|
id = OpaqueId.generate(alphabet: OpaqueId::STANDARD_ALPHABET)
|
@@ -73,7 +77,7 @@ id = OpaqueId.generate(size: 16, alphabet: hex_alphabet)
|
|
73
77
|
# URL-safe characters
|
74
78
|
url_safe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
75
79
|
id = OpaqueId.generate(size: 12, alphabet: url_safe)
|
76
|
-
# => "
|
80
|
+
# => "izkpm55j334u8x"
|
77
81
|
```
|
78
82
|
|
79
83
|
## ActiveRecord Integration
|
@@ -94,7 +98,7 @@ end
|
|
94
98
|
# Create a new user - opaque_id is automatically generated
|
95
99
|
user = User.create!(name: "John Doe", email: "john@example.com")
|
96
100
|
puts user.opaque_id
|
97
|
-
# => "
|
101
|
+
# => "izkpm55j334u8x9y2a"
|
98
102
|
|
99
103
|
# The ID is generated before the record is saved
|
100
104
|
user = User.new(name: "Jane Smith")
|
@@ -103,17 +107,17 @@ puts user.opaque_id
|
|
103
107
|
|
104
108
|
user.save!
|
105
109
|
puts user.opaque_id
|
106
|
-
# => "
|
110
|
+
# => "k8jh2mn9pl3qr7st1va" (generated on save)
|
107
111
|
```
|
108
112
|
|
109
113
|
### Finder Methods
|
110
114
|
|
111
115
|
```ruby
|
112
116
|
# Find by opaque_id (returns nil if not found)
|
113
|
-
user = User.find_by_opaque_id("
|
117
|
+
user = User.find_by_opaque_id("izkpm55j334u8x9y2a")
|
114
118
|
|
115
119
|
# Find by opaque_id (raises exception if not found)
|
116
|
-
user = User.find_by_opaque_id!("
|
120
|
+
user = User.find_by_opaque_id!("izkpm55j334u8x9y2a")
|
117
121
|
# => ActiveRecord::RecordNotFound if not found
|
118
122
|
|
119
123
|
# Use in scopes
|
@@ -123,7 +127,7 @@ class User < ApplicationRecord
|
|
123
127
|
scope :by_opaque_id, ->(id) { where(opaque_id: id) }
|
124
128
|
end
|
125
129
|
|
126
|
-
users = User.by_opaque_id("
|
130
|
+
users = User.by_opaque_id("izkpm55j334u8x9y2a")
|
127
131
|
```
|
128
132
|
|
129
133
|
### Custom Column Names
|
@@ -139,9 +143,9 @@ end
|
|
139
143
|
# Now the methods use the custom column name
|
140
144
|
user = User.create!(name: "John Doe")
|
141
145
|
puts user.public_id
|
142
|
-
# => "
|
146
|
+
# => "izkpm55j334u8x9y2a"
|
143
147
|
|
144
|
-
user = User.find_by_public_id("
|
148
|
+
user = User.find_by_public_id("izkpm55j334u8x9y2a")
|
145
149
|
```
|
146
150
|
|
147
151
|
## Rails Generator
|
@@ -198,7 +202,7 @@ end
|
|
198
202
|
# Create an order
|
199
203
|
order = Order.create!(user_id: 1, total: 99.99)
|
200
204
|
puts order.opaque_id
|
201
|
-
# => "
|
205
|
+
# => "izkpm55j334u8x"
|
202
206
|
|
203
207
|
# Use in URLs
|
204
208
|
order_url(order.opaque_id)
|
@@ -259,7 +263,7 @@ end
|
|
259
263
|
# Create a user
|
260
264
|
user = User.create!(name: "John Doe", email: "john@example.com")
|
261
265
|
puts user.opaque_id
|
262
|
-
# => "
|
266
|
+
# => "izkpm55j334u8x9y2a" (starts with letter)
|
263
267
|
|
264
268
|
# Use in user profiles
|
265
269
|
user_url(user.opaque_id)
|
@@ -273,7 +277,7 @@ user_url(user.opaque_id)
|
|
273
277
|
```ruby
|
274
278
|
# Generate multiple IDs at once
|
275
279
|
ids = 100.times.map { OpaqueId.generate }
|
276
|
-
# => ["
|
280
|
+
# => ["izkpm55j334u8x9y2a", "k8jh2mn9pl3qr7st1va", ...]
|
277
281
|
|
278
282
|
# Use in bulk operations
|
279
283
|
users_data = ids.map.with_index do |id, index|
|
@@ -15,8 +15,12 @@ module OpaqueId
|
|
15
15
|
class_option :column_name, type: :string, default: 'opaque_id',
|
16
16
|
desc: 'Name of the column to add'
|
17
17
|
|
18
|
+
class_option :limit, type: :numeric, default: 21,
|
19
|
+
desc: 'Column limit for the opaque_id string (default: 21)'
|
20
|
+
|
18
21
|
def create_migration_file
|
19
22
|
if model_name.present?
|
23
|
+
validate_limit_option
|
20
24
|
table_name = model_name.tableize
|
21
25
|
migration_template 'migration.rb.tt',
|
22
26
|
"db/migrate/add_opaque_id_to_#{table_name}.rb"
|
@@ -26,11 +30,20 @@ module OpaqueId
|
|
26
30
|
say 'Usage: rails generate opaque_id:install ModelName', :red
|
27
31
|
say 'Example: rails generate opaque_id:install User', :green
|
28
32
|
say 'Example: rails generate opaque_id:install Post --column-name=public_id', :green
|
33
|
+
say 'Example: rails generate opaque_id:install Post --limit=25', :green
|
29
34
|
end
|
30
35
|
end
|
31
36
|
|
32
37
|
private
|
33
38
|
|
39
|
+
def validate_limit_option
|
40
|
+
# Default OpaqueId length is 18, so limit should be at least that
|
41
|
+
return unless options[:limit] < 18
|
42
|
+
|
43
|
+
say "Warning: Column limit (#{options[:limit]}) is less than the default OpaqueId length (18).", :yellow
|
44
|
+
say 'Consider using --limit=21 or higher to avoid truncation issues.', :yellow
|
45
|
+
end
|
46
|
+
|
34
47
|
def add_include_to_model
|
35
48
|
model_path = "app/models/#{model_name.underscore}.rb"
|
36
49
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
class AddOpaqueIdTo<%=
|
1
|
+
class AddOpaqueIdTo<%= model_name.tableize.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
2
2
|
def change
|
3
|
-
add_column :<%=
|
4
|
-
add_index :<%=
|
3
|
+
add_column :<%= model_name.tableize %>, :<%= options[:column_name] %>, :string, limit: <%= options[:limit] %>
|
4
|
+
add_index :<%= model_name.tableize %>, :<%= options[:column_name] %>, unique: true
|
5
5
|
end
|
6
6
|
end
|
data/lib/opaque_id/model.rb
CHANGED
@@ -30,7 +30,7 @@ module OpaqueId
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def opaque_id_length
|
33
|
-
@opaque_id_length ||=
|
33
|
+
@opaque_id_length ||= 18
|
34
34
|
end
|
35
35
|
|
36
36
|
def opaque_id_length=(value)
|
@@ -38,7 +38,7 @@ module OpaqueId
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def opaque_id_alphabet
|
41
|
-
@opaque_id_alphabet ||= OpaqueId::
|
41
|
+
@opaque_id_alphabet ||= OpaqueId::SLUG_LIKE_ALPHABET
|
42
42
|
end
|
43
43
|
|
44
44
|
def opaque_id_alphabet=(value)
|
data/lib/opaque_id/version.rb
CHANGED
data/lib/opaque_id.rb
CHANGED
@@ -9,6 +9,9 @@ module OpaqueId
|
|
9
9
|
class GenerationError < Error; end
|
10
10
|
class ConfigurationError < Error; end
|
11
11
|
|
12
|
+
# Slug-like alphabet for URL-safe, double-click selectable IDs (36 characters)
|
13
|
+
SLUG_LIKE_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'
|
14
|
+
|
12
15
|
# Standard URL-safe alphabet (64 characters)
|
13
16
|
STANDARD_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
|
14
17
|
|
@@ -17,7 +20,7 @@ module OpaqueId
|
|
17
20
|
|
18
21
|
class << self
|
19
22
|
# Generate a cryptographically secure random ID
|
20
|
-
def generate(size:
|
23
|
+
def generate(size: 18, alphabet: SLUG_LIKE_ALPHABET)
|
21
24
|
raise ConfigurationError, 'Size must be positive' unless size.positive?
|
22
25
|
raise ConfigurationError, 'Alphabet cannot be empty' if alphabet.nil? || alphabet.empty?
|
23
26
|
|
data/release-please-config.json
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: opaque_id
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Nyaggah
|
@@ -140,6 +140,7 @@ files:
|
|
140
140
|
- docs/assets/css/custom.scss
|
141
141
|
- docs/assets/images/favicon.svg
|
142
142
|
- docs/assets/images/og-image.svg
|
143
|
+
- docs/benchmarks.md
|
143
144
|
- docs/configuration.md
|
144
145
|
- docs/development.md
|
145
146
|
- docs/getting-started.md
|
@@ -156,14 +157,6 @@ files:
|
|
156
157
|
- lib/opaque_id/model.rb
|
157
158
|
- lib/opaque_id/version.rb
|
158
159
|
- release-please-config.json
|
159
|
-
- tasks/0001-prd-opaque-id-gem.md
|
160
|
-
- tasks/0002-prd-publishing-release-automation.md
|
161
|
-
- tasks/0003-prd-documentation-site.md
|
162
|
-
- tasks/references/opaque_gem_requirements.md
|
163
|
-
- tasks/references/original_identifiable_concern_and_nanoid.md
|
164
|
-
- tasks/tasks-0001-prd-opaque-id-gem.md
|
165
|
-
- tasks/tasks-0002-prd-publishing-release-automation.md
|
166
|
-
- tasks/tasks-0003-prd-documentation-site.md
|
167
160
|
homepage: https://github.com/nyaggah/opaque_id
|
168
161
|
licenses:
|
169
162
|
- MIT
|
@@ -1,202 +0,0 @@
|
|
1
|
-
# Product Requirements Document: OpaqueId Ruby Gem
|
2
|
-
|
3
|
-
## Introduction/Overview
|
4
|
-
|
5
|
-
The OpaqueId gem is a Ruby library that generates cryptographically secure, collision-free opaque identifiers for ActiveRecord models. This gem replaces the existing `nanoid.rb` dependency by implementing the same functionality using Ruby's built-in `SecureRandom` methods, eliminating external dependencies while maintaining the same security and performance characteristics.
|
6
|
-
|
7
|
-
The primary problem this gem solves is preventing the exposure of incremental database IDs in public URLs and APIs, which can reveal business metrics, enable enumeration attacks, and expose internal system architecture. Instead, it provides opaque, non-sequential identifiers that are URL-friendly and cryptographically secure.
|
8
|
-
|
9
|
-
## Goals
|
10
|
-
|
11
|
-
1. **Replace nanoid.rb dependency** - Implement equivalent functionality using Ruby's built-in SecureRandom
|
12
|
-
2. **Maintain security standards** - Provide cryptographically secure ID generation with unbiased distribution
|
13
|
-
3. **Ensure performance** - Achieve 1M+ IDs/sec for 64-character alphabets, 180K+ IDs/sec for 36-character alphabets
|
14
|
-
4. **Simplify integration** - Provide seamless ActiveRecord integration with minimal configuration
|
15
|
-
5. **Enable wide adoption** - Create comprehensive documentation accessible to all Rails developers
|
16
|
-
6. **Ensure reliability** - Implement robust collision detection and retry logic
|
17
|
-
7. **Maintain compatibility** - Support Rails 8.0+ and Ruby 3.2+ environments
|
18
|
-
|
19
|
-
## User Stories
|
20
|
-
|
21
|
-
### Primary Users: Rails Developers
|
22
|
-
|
23
|
-
**As a Rails developer**, I want to generate opaque IDs for my models so that I can expose public identifiers without revealing database structure.
|
24
|
-
|
25
|
-
**As a Rails developer**, I want to easily integrate opaque ID generation into my existing models so that I don't have to manually implement ID generation logic.
|
26
|
-
|
27
|
-
**As a Rails developer**, I want to configure ID generation parameters (length, alphabet, column name) so that I can customize the behavior for different use cases.
|
28
|
-
|
29
|
-
**As a Rails developer**, I want to find records by their opaque ID so that I can build public-facing APIs and URLs.
|
30
|
-
|
31
|
-
**As a Rails developer**, I want the gem to handle collision detection automatically so that I don't have to worry about duplicate IDs.
|
32
|
-
|
33
|
-
**As a Rails developer**, I want comprehensive documentation and examples so that I can quickly understand and implement the gem in my project.
|
34
|
-
|
35
|
-
### Secondary Users: Security-Conscious Teams
|
36
|
-
|
37
|
-
**As a security-conscious developer**, I want cryptographically secure ID generation so that my public identifiers cannot be predicted or enumerated.
|
38
|
-
|
39
|
-
**As a security-conscious developer**, I want unbiased character distribution so that my IDs have maximum entropy and cannot be analyzed for patterns.
|
40
|
-
|
41
|
-
## Functional Requirements
|
42
|
-
|
43
|
-
### Core ID Generation
|
44
|
-
|
45
|
-
1. The system must generate cryptographically secure random IDs using Ruby's `SecureRandom`
|
46
|
-
2. The system must implement rejection sampling algorithm to ensure unbiased character distribution
|
47
|
-
3. The system must provide optimized fast path for 64-character alphabets using bitwise operations
|
48
|
-
4. The system must support custom alphabet configurations
|
49
|
-
5. The system must support custom ID length configurations
|
50
|
-
6. The system must provide two predefined alphabets: ALPHANUMERIC_ALPHABET (36 chars) and STANDARD_ALPHABET (64 chars)
|
51
|
-
|
52
|
-
### ActiveRecord Integration
|
53
|
-
|
54
|
-
7. The system must provide `OpaqueId::Model` concern for easy ActiveRecord integration
|
55
|
-
8. The system must automatically generate opaque IDs on model creation via `before_create` callback
|
56
|
-
9. The system must provide `find_by_opaque_id` and `find_by_opaque_id!` class methods
|
57
|
-
10. The system must support custom column names via `opaque_id_column` configuration
|
58
|
-
11. The system must implement collision detection with configurable retry attempts
|
59
|
-
12. The system must raise appropriate errors when collision resolution fails
|
60
|
-
|
61
|
-
### Rails Generator (Optional Convenience Tool)
|
62
|
-
|
63
|
-
13. The system must provide optional Rails generator `opaque_id:install` for creating migrations and updating models
|
64
|
-
14. The system must generate migration files that add opaque_id column with unique index
|
65
|
-
15. The system must automatically add `include OpaqueId::Model` to the corresponding model file
|
66
|
-
16. The system must support custom column names via generator options
|
67
|
-
17. The system must require explicit table name argument and show clear usage instructions when run without arguments
|
68
|
-
18. The system must work with any existing table (new or existing models)
|
69
|
-
19. The system must handle edge cases gracefully (missing model file, already included, different class names)
|
70
|
-
|
71
|
-
### Configuration Options
|
72
|
-
|
73
|
-
20. The system must support `opaque_id_column` configuration (default: `:opaque_id`)
|
74
|
-
21. The system must support `opaque_id_length` configuration (default: `18`)
|
75
|
-
22. The system must support `opaque_id_alphabet` configuration (default: `ALPHANUMERIC_ALPHABET`)
|
76
|
-
23. The system must support `opaque_id_require_letter_start` configuration (default: `true`)
|
77
|
-
24. The system must support `opaque_id_purge_chars` configuration (default: `nil`)
|
78
|
-
25. The system must support `opaque_id_max_retry` configuration (default: `1000`)
|
79
|
-
|
80
|
-
### Error Handling
|
81
|
-
|
82
|
-
26. The system must raise `OpaqueId::ConfigurationError` for invalid size or empty alphabet
|
83
|
-
27. The system must raise `OpaqueId::GenerationError` when collision resolution fails
|
84
|
-
28. The system must provide clear error messages for debugging
|
85
|
-
|
86
|
-
### Standalone Usage
|
87
|
-
|
88
|
-
29. The system must provide `OpaqueId.generate` method for standalone ID generation
|
89
|
-
30. The system must support all configuration options in standalone generation
|
90
|
-
31. The system must maintain the same security and performance characteristics in standalone mode
|
91
|
-
|
92
|
-
## Non-Goals (Out of Scope)
|
93
|
-
|
94
|
-
1. **Other ORM Support** - Will not support Mongoid, Sequel, or other ORMs in initial release
|
95
|
-
2. **Non-Rails Usage** - Will not provide standalone Ruby usage without ActiveRecord dependency
|
96
|
-
3. **Custom Algorithms** - Will not implement alternative ID generation algorithms beyond rejection sampling
|
97
|
-
4. **Database Migrations** - Will not provide automatic database migration for existing records
|
98
|
-
5. **ID Validation** - Will not provide built-in ID format validation (users can implement their own)
|
99
|
-
6. **Bulk Generation** - Will not provide optimized bulk ID generation methods
|
100
|
-
7. **ID Prefixes/Suffixes** - Will not support adding prefixes or suffixes to generated IDs
|
101
|
-
8. **Custom Random Sources** - Will not support custom random number generators beyond SecureRandom
|
102
|
-
9. **Interactive Generator Mode** - Will not provide interactive prompts for generator arguments
|
103
|
-
10. **Backward Compatibility** - Will not maintain compatibility with existing `public_id` implementations
|
104
|
-
|
105
|
-
## Design Considerations
|
106
|
-
|
107
|
-
### API Design
|
108
|
-
|
109
|
-
- Follow Rails conventions for ActiveRecord concerns and generators
|
110
|
-
- Use descriptive method names that clearly indicate functionality
|
111
|
-
- Provide both safe (`find_by_opaque_id`) and unsafe (`find_by_opaque_id!`) lookup methods
|
112
|
-
- Use class-level configuration options for easy customization
|
113
|
-
- Follow Devise-style generator pattern for seamless integration
|
114
|
-
|
115
|
-
### Performance Optimization
|
116
|
-
|
117
|
-
- Implement fast path for 64-character alphabets using bitwise operations (`byte & 63`)
|
118
|
-
- Use rejection sampling with optimal mask calculation for unbiased distribution
|
119
|
-
- Pre-allocate string capacity to avoid memory reallocation during generation
|
120
|
-
- Batch random byte generation to minimize SecureRandom calls
|
121
|
-
|
122
|
-
### Security Considerations
|
123
|
-
|
124
|
-
- Use only cryptographically secure random number generation
|
125
|
-
- Implement proper rejection sampling to avoid modulo bias
|
126
|
-
- Provide sufficient entropy through configurable alphabet sizes
|
127
|
-
- Ensure IDs cannot be predicted or enumerated
|
128
|
-
|
129
|
-
## Technical Considerations
|
130
|
-
|
131
|
-
### Dependencies
|
132
|
-
|
133
|
-
- **ActiveRecord**: >= 6.0 (targeting Rails 8.0+ compatibility)
|
134
|
-
- **ActiveSupport**: >= 6.0 (for concern functionality)
|
135
|
-
- **Ruby**: >= 3.2 (for modern Ruby features and performance)
|
136
|
-
|
137
|
-
### Testing Framework
|
138
|
-
|
139
|
-
- Use **Minitest** instead of RSpec for consistency with Rails conventions
|
140
|
-
- Implement comprehensive unit tests for all public methods
|
141
|
-
- Include statistical tests for character distribution uniformity
|
142
|
-
- Add performance benchmarks to ensure performance requirements are met
|
143
|
-
- Test edge cases including collision scenarios and error conditions
|
144
|
-
|
145
|
-
### File Structure
|
146
|
-
|
147
|
-
```
|
148
|
-
lib/
|
149
|
-
├── opaque_id.rb # Main module with generator
|
150
|
-
├── opaque_id/
|
151
|
-
│ ├── version.rb # Version constant
|
152
|
-
│ └── model.rb # ActiveRecord concern
|
153
|
-
└── generators/
|
154
|
-
└── opaque_id/
|
155
|
-
├── install_generator.rb # Migration generator
|
156
|
-
└── templates/
|
157
|
-
└── migration.rb.tt # Migration template
|
158
|
-
```
|
159
|
-
|
160
|
-
### Error Classes
|
161
|
-
|
162
|
-
- `OpaqueId::Error` - Base error class
|
163
|
-
- `OpaqueId::GenerationError` - ID generation failures
|
164
|
-
- `OpaqueId::ConfigurationError` - Invalid configuration
|
165
|
-
|
166
|
-
## Success Metrics
|
167
|
-
|
168
|
-
### Performance Metrics
|
169
|
-
|
170
|
-
- **Standard alphabet (64 chars)**: Achieve ~1.2M IDs/sec generation rate
|
171
|
-
- **Alphanumeric alphabet (36 chars)**: Achieve ~180K IDs/sec generation rate
|
172
|
-
- **Custom alphabet (20 chars)**: Achieve ~150K IDs/sec generation rate
|
173
|
-
|
174
|
-
### Quality Metrics
|
175
|
-
|
176
|
-
- **Test Coverage**: Achieve 95%+ code coverage
|
177
|
-
- **Documentation**: Provide comprehensive README with examples
|
178
|
-
- **Error Handling**: All error conditions properly tested and documented
|
179
|
-
|
180
|
-
### Adoption Metrics
|
181
|
-
|
182
|
-
- **Gem Downloads**: Target 1000+ downloads in first month
|
183
|
-
- **GitHub Stars**: Target 50+ stars within 6 months
|
184
|
-
- **Community Feedback**: Positive reception from Rails community
|
185
|
-
|
186
|
-
## Open Questions
|
187
|
-
|
188
|
-
1. **Version Strategy**: Should we follow semantic versioning strictly, or use a different versioning strategy for a utility gem?
|
189
|
-
|
190
|
-
2. **Backward Compatibility**: How should we handle potential breaking changes in future versions, especially regarding default configurations?
|
191
|
-
|
192
|
-
3. **Performance Testing**: Should we include automated performance regression testing in CI/CD, or rely on manual benchmarking?
|
193
|
-
|
194
|
-
4. **Documentation Hosting**: Should we create a dedicated documentation site, or rely on GitHub README and inline documentation?
|
195
|
-
|
196
|
-
5. **Community Contributions**: What level of community contribution should we expect, and how should we structure the project to encourage contributions?
|
197
|
-
|
198
|
-
6. **Integration Testing**: Should we test against multiple Rails versions in CI, or focus on the target Rails 8.0+ range?
|
199
|
-
|
200
|
-
7. **Security Auditing**: Should we implement any security auditing tools or processes for the random number generation?
|
201
|
-
|
202
|
-
8. **Migration Path**: How should we help users migrate from nanoid.rb to opaque_id, if at all?
|
@@ -1,206 +0,0 @@
|
|
1
|
-
# Product Requirements Document: Publishing & Release Automation
|
2
|
-
|
3
|
-
## Introduction/Overview
|
4
|
-
|
5
|
-
This PRD outlines the implementation of a fully automated publishing and release system for the OpaqueId Ruby gem. The system will automatically version, test, and publish releases to RubyGems.org based on conventional commits, eliminating manual release processes and ensuring consistent, professional releases.
|
6
|
-
|
7
|
-
**Problem**: Manual release processes are error-prone, time-consuming, and inconsistent. Without automated versioning and publishing, releases can be delayed, version numbers can be inconsistent, and changelogs can be incomplete.
|
8
|
-
|
9
|
-
**Goal**: Implement a fully automated CI/CD pipeline that handles versioning, testing, changelog generation, and publishing based on conventional commits and the state of the main branch.
|
10
|
-
|
11
|
-
## Goals
|
12
|
-
|
13
|
-
1. **Automated Versioning**: Automatically determine version bumps (patch/minor/major) based on conventional commit types
|
14
|
-
2. **Automated Publishing**: Automatically publish to RubyGems.org when changes are ready
|
15
|
-
3. **Quality Gates**: Ensure all tests pass, code quality standards are met, and security checks pass before release
|
16
|
-
4. **Automated Changelog**: Generate changelog entries from conventional commits
|
17
|
-
5. **Dependency Management**: Automatically check for and update dependencies weekly
|
18
|
-
6. **Commit Standardization**: Enforce conventional commit format for consistent messaging
|
19
|
-
7. **Security**: Use RubyGems trusted publishing for secure, keyless releases
|
20
|
-
|
21
|
-
## User Stories
|
22
|
-
|
23
|
-
### As a Developer
|
24
|
-
|
25
|
-
- **US1**: I want my commits to automatically trigger appropriate version bumps so that I don't have to manually manage version numbers
|
26
|
-
- **US2**: I want releases to be published automatically when I push to main so that I don't have to remember to manually publish
|
27
|
-
- **US3**: I want commit message validation so that I follow consistent formatting standards
|
28
|
-
- **US4**: I want automated dependency updates so that my gem stays secure and up-to-date
|
29
|
-
|
30
|
-
### As a Maintainer
|
31
|
-
|
32
|
-
- **US5**: I want automated testing and quality checks before release so that I can trust the published code
|
33
|
-
- **US6**: I want automated changelog generation so that users know what changed in each release
|
34
|
-
- **US7**: I want security scanning before release so that vulnerabilities are caught early
|
35
|
-
|
36
|
-
### As a User
|
37
|
-
|
38
|
-
- **US8**: I want consistent, predictable releases so that I can trust the gem's stability
|
39
|
-
- **US9**: I want clear changelogs so that I understand what changed between versions
|
40
|
-
|
41
|
-
## Functional Requirements
|
42
|
-
|
43
|
-
### 1. Conventional Commits Integration
|
44
|
-
|
45
|
-
1.1. **Commit Message Validation**: Enforce conventional commit format (feat:, fix:, docs:, etc.) on all commits
|
46
|
-
1.2. **Commit Linting**: Use commitlint or similar tool to validate commit message format
|
47
|
-
1.3. **Pre-commit Hooks**: Automatically validate commit messages before they are accepted
|
48
|
-
1.4. **Commit Types**: Support standard types: feat, fix, docs, style, refactor, perf, test, chore, ci, build
|
49
|
-
|
50
|
-
### 2. Automated Versioning
|
51
|
-
|
52
|
-
2.1. **Semantic Versioning**: Use semantic versioning (MAJOR.MINOR.PATCH) based on conventional commits
|
53
|
-
2.2. **Version Bump Logic**:
|
54
|
-
|
55
|
-
- `feat:` commits → MINOR version bump
|
56
|
-
- `fix:` commits → PATCH version bump
|
57
|
-
- `BREAKING CHANGE:` or `!` → MAJOR version bump
|
58
|
-
- Other types → no version bump
|
59
|
-
2.3. **Version Detection**: Automatically detect when version should be bumped based on unreleased commits
|
60
|
-
2.4. **Version File Update**: Automatically update `lib/opaque_id/version.rb` with new version
|
61
|
-
|
62
|
-
### 3. GitHub Actions Workflow
|
63
|
-
|
64
|
-
3.1. **Single Workflow**: Update existing `main.yml` to include release automation
|
65
|
-
3.2. **Trigger Conditions**: Run on push to main branch when unreleased changes exist
|
66
|
-
3.3. **Workflow Steps**:
|
67
|
-
|
68
|
-
- Checkout code
|
69
|
-
- Setup Ruby environment
|
70
|
-
- Install dependencies
|
71
|
-
- Run tests
|
72
|
-
- Run RuboCop
|
73
|
-
- Security audit
|
74
|
-
- Determine version bump
|
75
|
-
- Update version file
|
76
|
-
- Generate changelog
|
77
|
-
- Create git tag
|
78
|
-
- Publish to RubyGems.org
|
79
|
-
- Create GitHub release
|
80
|
-
|
81
|
-
### 4. Quality Gates
|
82
|
-
|
83
|
-
4.1. **Test Execution**: Run full test suite before any release
|
84
|
-
4.2. **Code Quality**: Run RuboCop and fail if violations exist
|
85
|
-
4.3. **Security Scanning**: Run `bundle audit` to check for vulnerable dependencies
|
86
|
-
4.4. **Dependency Check**: Ensure all dependencies are up-to-date and secure
|
87
|
-
|
88
|
-
### 5. Changelog Generation
|
89
|
-
|
90
|
-
5.1. **Automated Generation**: Generate changelog from conventional commits since last release
|
91
|
-
5.2. **Changelog Format**: Use conventional changelog format with categorized sections
|
92
|
-
5.3. **Changelog Sections**: Features, Bug Fixes, Breaking Changes, Documentation, etc.
|
93
|
-
5.4. **Changelog Update**: Automatically update `CHANGELOG.md` with new entries
|
94
|
-
|
95
|
-
### 6. RubyGems Publishing
|
96
|
-
|
97
|
-
6.1. **Trusted Publishing**: Use RubyGems trusted publishing (no API keys required)
|
98
|
-
6.2. **MFA Enforcement**: Ensure MFA is required for publishing (already configured)
|
99
|
-
6.3. **Build Process**: Build gem package before publishing
|
100
|
-
6.4. **Publish Process**: Automatically push to RubyGems.org when all checks pass
|
101
|
-
|
102
|
-
### 7. Dependabot Integration
|
103
|
-
|
104
|
-
7.1. **Weekly Updates**: Check for dependency updates every Monday at 9 AM
|
105
|
-
7.2. **Update Types**: Check for both direct and indirect dependency updates
|
106
|
-
7.3. **Security Updates**: Prioritize security-related updates
|
107
|
-
7.4. **Update Strategy**: Create pull requests for dependency updates
|
108
|
-
|
109
|
-
### 8. Git Tag Management
|
110
|
-
|
111
|
-
8.1. **Automatic Tagging**: Create git tags for each release (e.g., `v1.2.3`)
|
112
|
-
8.2. **Tag Format**: Use semantic versioning format with `v` prefix
|
113
|
-
8.3. **Tag Push**: Automatically push tags to GitHub repository
|
114
|
-
8.4. **GitHub Release**: Create GitHub release with changelog and tag
|
115
|
-
|
116
|
-
## Non-Goals (Out of Scope)
|
117
|
-
|
118
|
-
1. **Manual Release Override**: No manual release triggers or overrides
|
119
|
-
2. **Multiple Branch Support**: Only support releases from main branch
|
120
|
-
3. **Pre-release Versions**: No support for alpha/beta/rc versions initially
|
121
|
-
4. **Monorepo Support**: Single gem repository only
|
122
|
-
5. **Custom Versioning Logic**: No custom versioning rules beyond conventional commits
|
123
|
-
6. **Rollback Automation**: No automatic rollback of failed releases
|
124
|
-
|
125
|
-
## Technical Considerations
|
126
|
-
|
127
|
-
### Dependencies
|
128
|
-
|
129
|
-
- **Release Please**: Google's tool for PR-based versioning and changelog generation
|
130
|
-
- **Bundler's release tasks**: Built-in rake tasks for building and tagging (already present)
|
131
|
-
- **commitlint**: For commit message validation (optional)
|
132
|
-
- **bundle-audit**: For security scanning
|
133
|
-
|
134
|
-
### GitHub Actions
|
135
|
-
|
136
|
-
- **googleapis/release-please-action**: For creating release PRs and versioning
|
137
|
-
- **rubygems/release-gem**: For RubyGems publishing with trusted publishing
|
138
|
-
- **actions/checkout**: For code checkout
|
139
|
-
- **ruby/setup-ruby**: For Ruby environment setup
|
140
|
-
|
141
|
-
### Configuration Files
|
142
|
-
|
143
|
-
- **release-please-config.json**: Release Please configuration
|
144
|
-
- **dependabot.yml**: Dependency update configuration
|
145
|
-
- **.github/workflows/release-please.yml**: Release PR workflow
|
146
|
-
- **.github/workflows/publish.yml**: Publishing workflow
|
147
|
-
|
148
|
-
### RubyGems Trusted Publishing
|
149
|
-
|
150
|
-
- Configure trusted publishing on RubyGems.org
|
151
|
-
- Use GitHub OIDC for authentication
|
152
|
-
- No API keys or secrets required
|
153
|
-
|
154
|
-
## Success Metrics
|
155
|
-
|
156
|
-
1. **Release Automation**: 100% of releases are automated (no manual intervention)
|
157
|
-
2. **Release Frequency**: Ability to release multiple times per day if needed
|
158
|
-
3. **Quality Gates**: 0% of releases with failing tests or security issues
|
159
|
-
4. **Commit Compliance**: 100% of commits follow conventional commit format
|
160
|
-
5. **Dependency Currency**: Dependencies updated within 7 days of availability
|
161
|
-
6. **Release Time**: From commit to published gem in under 10 minutes
|
162
|
-
|
163
|
-
## Open Questions
|
164
|
-
|
165
|
-
1. **Commit Message Enforcement**: Should we use pre-commit hooks or GitHub Actions validation?
|
166
|
-
2. **Changelog Format**: Should we use conventional-changelog or custom format?
|
167
|
-
3. **Version Bump Strategy**: Should we batch multiple commits into single releases?
|
168
|
-
4. **Rollback Strategy**: How should we handle failed releases or rollbacks?
|
169
|
-
5. **Notification Strategy**: Should we notify maintainers of successful/failed releases?
|
170
|
-
6. **Branch Protection**: Should we require status checks before merging to main?
|
171
|
-
|
172
|
-
## Implementation Priority
|
173
|
-
|
174
|
-
### Phase 1: Core Automation
|
175
|
-
|
176
|
-
- Conventional commits validation
|
177
|
-
- Automated versioning
|
178
|
-
- Basic GitHub Actions workflow
|
179
|
-
- RubyGems publishing
|
180
|
-
|
181
|
-
### Phase 2: Quality & Security
|
182
|
-
|
183
|
-
- Quality gates (tests, RuboCop, security)
|
184
|
-
- Automated changelog generation
|
185
|
-
- Dependabot integration
|
186
|
-
|
187
|
-
### Phase 3: Polish & Monitoring
|
188
|
-
|
189
|
-
- Enhanced notifications
|
190
|
-
- Release monitoring
|
191
|
-
- Performance optimization
|
192
|
-
|
193
|
-
## Acceptance Criteria
|
194
|
-
|
195
|
-
- [ ] All commits follow conventional commit format
|
196
|
-
- [ ] Version numbers are automatically bumped based on commit types
|
197
|
-
- [ ] Releases are automatically published to RubyGems.org
|
198
|
-
- [ ] Changelog is automatically generated from commits
|
199
|
-
- [ ] All tests pass before release
|
200
|
-
- [ ] RuboCop passes before release
|
201
|
-
- [ ] Security audit passes before release
|
202
|
-
- [ ] Dependencies are automatically updated weekly
|
203
|
-
- [ ] Git tags are automatically created for releases
|
204
|
-
- [ ] GitHub releases are automatically created
|
205
|
-
- [ ] RubyGems trusted publishing is configured
|
206
|
-
- [ ] No manual intervention required for releases
|