encoded_ids 1.0.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 +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +21 -0
- data/LICENSE.md +21 -0
- data/README.md +254 -0
- data/Rakefile +8 -0
- data/encoded_ids.gemspec +33 -0
- data/examples/controller.rb +52 -0
- data/examples/initializer.rb +30 -0
- data/examples/models.rb +49 -0
- data/lib/encoded_ids/configuration.rb +28 -0
- data/lib/encoded_ids/controller_helpers.rb +41 -0
- data/lib/encoded_ids/hashid_identifiable.rb +117 -0
- data/lib/encoded_ids/railtie.rb +22 -0
- data/lib/encoded_ids/uuid_identifiable.rb +162 -0
- data/lib/encoded_ids/version.rb +5 -0
- data/lib/encoded_ids.rb +27 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 31d25d05fa2a21196dee435efb25c144e2cf90ee3837c8da263f49c22f1e8825
|
|
4
|
+
data.tar.gz: 54af5781a3455a70b96b5033e6dc9e58b1ea0c8cc3b64a0afc4b5dbe83fd5c35
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: bebf4a30fd0307ad7514e57088cca68bfab718789ef7d5d13506bd6bf312caf3d3f02442e1d87186c3fc5e3c5c9dbbf2dbf84fd71188537af41472a1048a3583
|
|
7
|
+
data.tar.gz: b83f9254cae57f08270f9c4da0db06fef2883b07641ad20d0fe6f00b6e25d60b2a3ca1737d2cd993b84ea144140de382268f29bfd5af9bfdcc3bf8738dbbdd6e
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-02-09
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial stable release
|
|
12
|
+
- `HashidEncodedIds` concern for integer primary keys
|
|
13
|
+
- `UuidEncodedIds` concern for UUID primary keys
|
|
14
|
+
- Automatic `to_param` override for Rails URL generation
|
|
15
|
+
- Overridden `find` method to accept both internal and public IDs
|
|
16
|
+
- `find_by_public_id` and `find_by_public_id!` class methods
|
|
17
|
+
- Controller helpers: `find_by_any_id` and `find_by_any_id!`
|
|
18
|
+
- Compositional prefix support with `add_public_id_segment`
|
|
19
|
+
- Configurable hash length for hashids
|
|
20
|
+
- Global configuration via `EncodedIds.configure`
|
|
21
|
+
- Automatic Rails integration via Railtie
|
data/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jasper Mayone
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# EncodedIds
|
|
2
|
+
|
|
3
|
+
Stripe-like public IDs for Rails models. Generate API-friendly identifiers like `usr_k5qx9z` or `org_4k8xJm2pN9qW` that:
|
|
4
|
+
|
|
5
|
+
- Hide sequential integer IDs from your API
|
|
6
|
+
- Provide type context in the ID itself (the prefix tells you it's a user, organization, etc.)
|
|
7
|
+
- Work seamlessly with Rails routing and controllers
|
|
8
|
+
- Support both integer and UUID primary keys
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
Add to your Gemfile:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
gem 'encoded_ids'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or install directly:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
gem install encoded_ids
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### For models with integer IDs (uses hashids):
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
class User < ApplicationRecord
|
|
30
|
+
include EncodedIds::HashidIdentifiable
|
|
31
|
+
set_public_id_prefix :usr
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
user = User.first
|
|
35
|
+
user.public_id # => "usr_k5qx9z"
|
|
36
|
+
user.to_param # => "k5qx9z" (used in URLs - no prefix by default)
|
|
37
|
+
|
|
38
|
+
# Find by public_id (with or without prefix)
|
|
39
|
+
User.find("k5qx9z") # => <User id: 1>
|
|
40
|
+
User.find("usr_k5qx9z") # => <User id: 1>
|
|
41
|
+
User.find_by_public_id("usr_k5qx9z") # => <User id: 1>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### For models with UUID IDs (uses base62 encoding):
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
class Organization < ApplicationRecord
|
|
48
|
+
include EncodedIds::UuidIdentifiable
|
|
49
|
+
set_public_id_prefix "org"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
org = Organization.first
|
|
53
|
+
org.public_id # => "org_4k8xJm2pN9qW"
|
|
54
|
+
org.to_param # => "4k8xJm2pN9qW" (no prefix by default)
|
|
55
|
+
|
|
56
|
+
# Find by public_id (with or without prefix)
|
|
57
|
+
Organization.find("4k8xJm2pN9qW") # => <Organization id: "uuid...">
|
|
58
|
+
Organization.find("org_4k8xJm2pN9qW") # => <Organization id: "uuid...">
|
|
59
|
+
Organization.find_by_public_id("org_4k8xJm2pN9qW") # => <Organization id: "uuid...">
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
### Automatic URL Parameter Handling
|
|
65
|
+
|
|
66
|
+
The gem overrides `to_param` automatically, so Rails will use the hashid/encoded ID in all your URLs:
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
link_to "View User", user_path(user)
|
|
70
|
+
# => /users/k5qx9z (clean URLs without prefix by default)
|
|
71
|
+
|
|
72
|
+
redirect_to @user
|
|
73
|
+
# => /users/k5qx9z
|
|
74
|
+
|
|
75
|
+
# You can still use the full public_id with prefix if needed:
|
|
76
|
+
User.find_by_public_id("usr_k5qx9z") # Works!
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Overridden `find` Method
|
|
80
|
+
|
|
81
|
+
The `find` method is automatically enhanced to accept internal IDs, hashids, and full public IDs:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
# These all work:
|
|
85
|
+
User.find(1) # Regular internal ID
|
|
86
|
+
User.find("k5qx9z") # Hashid (no prefix)
|
|
87
|
+
User.find("usr_k5qx9z") # Full public ID (with prefix)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Compositional Prefixes (Hashid only)
|
|
91
|
+
|
|
92
|
+
For namespaced models, you can build prefixes from multiple segments:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
class Intel::Tool::PhoneNumber < ApplicationRecord
|
|
96
|
+
include EncodedIds::HashidIdentifiable
|
|
97
|
+
add_public_id_segment :int
|
|
98
|
+
add_public_id_segment :tool
|
|
99
|
+
add_public_id_segment :phn
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
phone.public_id # => "int_tool_phn_k5qx9z"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Configurable Hash Length
|
|
106
|
+
|
|
107
|
+
For tables with many records, increase the minimum hash length:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
class Enrollment < ApplicationRecord
|
|
111
|
+
include EncodedIds::HashidIdentifiable
|
|
112
|
+
set_public_id_prefix :enr, min_hash_length: 12
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
enrollment.public_id # => "enr_x5qp9z2m8n4k"
|
|
116
|
+
enrollment.to_param # => "x5qp9z2m8n4k"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Route Behavior Configuration
|
|
120
|
+
|
|
121
|
+
By default, `to_param` returns just the hash (no prefix) for cleaner URLs. You can change this globally or per-model:
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
# Global configuration - include prefix in all URLs (Stripe style)
|
|
125
|
+
EncodedIds.configure do |config|
|
|
126
|
+
config.use_prefix_in_routes = true
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Per-model override
|
|
130
|
+
class User < ApplicationRecord
|
|
131
|
+
include EncodedIds::HashidIdentifiable
|
|
132
|
+
set_public_id_prefix :usr, use_prefix_in_routes: true
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
user.to_param # => "usr_k5qx9z" instead of just "k5qx9z"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Controller Helpers
|
|
139
|
+
|
|
140
|
+
Automatically included in all controllers:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
class UsersController < ApplicationController
|
|
144
|
+
def show
|
|
145
|
+
# Accepts both internal ID and public_id
|
|
146
|
+
@user = find_by_any_id(User, params[:id])
|
|
147
|
+
|
|
148
|
+
# Or with bang method (raises RecordNotFound)
|
|
149
|
+
@user = find_by_any_id!(User, params[:id])
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Configuration
|
|
155
|
+
|
|
156
|
+
Create an initializer at `config/initializers/encoded_ids.rb`:
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
EncodedIds.configure do |config|
|
|
160
|
+
# Hashid configuration (for integer IDs)
|
|
161
|
+
config.hashid_salt = Rails.application.credentials.dig(:hashid, :salt)
|
|
162
|
+
config.hashid_min_length = 8
|
|
163
|
+
config.hashid_alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
164
|
+
|
|
165
|
+
# Base62 alphabet (for UUID encoding)
|
|
166
|
+
config.base62_alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
167
|
+
|
|
168
|
+
# Separator between prefix and hash
|
|
169
|
+
config.separator = "_"
|
|
170
|
+
|
|
171
|
+
# Whether to include prefix in to_param URLs
|
|
172
|
+
# false (default) = /users/k5qx9z
|
|
173
|
+
# true = /users/usr_k5qx9z (Stripe style)
|
|
174
|
+
config.use_prefix_in_routes = false
|
|
175
|
+
end
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Important:** For production, set a unique `hashid_salt` in your credentials:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
rails credentials:edit
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
```yaml
|
|
185
|
+
hashid:
|
|
186
|
+
salt: your-unique-salt-here # Generate with: SecureRandom.hex(32)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## How It Works
|
|
190
|
+
|
|
191
|
+
### Integer IDs (HashidIdentifiable)
|
|
192
|
+
|
|
193
|
+
Uses [hashid-rails](https://github.com/jcypret/hashid-rails) to encode integer IDs into short, URL-safe strings. The encoding is:
|
|
194
|
+
- Reversible (can decode back to the integer ID)
|
|
195
|
+
- Obfuscated (not sequential, not easily guessable)
|
|
196
|
+
- Short (configurable minimum length)
|
|
197
|
+
|
|
198
|
+
### UUID IDs (UuidIdentifiable)
|
|
199
|
+
|
|
200
|
+
Uses base62 encoding to shorten UUIDs from 36 characters to ~22 characters. Since UUIDs are already random and non-sequential, this just adds the prefix and shortens the representation.
|
|
201
|
+
|
|
202
|
+
## API
|
|
203
|
+
|
|
204
|
+
### Model Methods (both types)
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
# Instance methods
|
|
208
|
+
model.public_id # Returns the full public ID
|
|
209
|
+
model.to_param # Returns the public ID (used by Rails in URLs)
|
|
210
|
+
|
|
211
|
+
# Class methods
|
|
212
|
+
Model.find(id) # Accepts both internal and public IDs
|
|
213
|
+
Model.find_by_public_id(id) # Only finds by public ID
|
|
214
|
+
Model.find_by_public_id!(id) # Like above, but raises if not found
|
|
215
|
+
Model.get_public_id_prefix # Returns the configured prefix
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Controller Methods
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
find_by_any_id(Model, id) # Returns record or nil
|
|
222
|
+
find_by_any_id!(Model, id) # Returns record or raises RecordNotFound
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Migration from Existing Code
|
|
226
|
+
|
|
227
|
+
If you have existing `PublicIdentifiable` concerns in your app:
|
|
228
|
+
|
|
229
|
+
1. Add `encoded_ids` to your Gemfile
|
|
230
|
+
2. Replace `include PublicIdentifiable` with:
|
|
231
|
+
- `include EncodedIds::HashidIdentifiable` (for integer IDs)
|
|
232
|
+
- `include EncodedIds::UuidIdentifiable` (for UUID IDs)
|
|
233
|
+
3. Update your hashid initializer to use `EncodedIds.configure`
|
|
234
|
+
4. Remove your old `PublicIdentifiable` concern
|
|
235
|
+
5. Controllers automatically get the helper methods
|
|
236
|
+
|
|
237
|
+
## Why Public IDs?
|
|
238
|
+
|
|
239
|
+
Public IDs provide several benefits:
|
|
240
|
+
|
|
241
|
+
1. **Security**: Don't expose sequential integer IDs that leak information about your data volume
|
|
242
|
+
2. **Type Safety**: The prefix makes it obvious what type of resource an ID refers to
|
|
243
|
+
3. **API Ergonomics**: Easier to debug and understand API calls
|
|
244
|
+
4. **Future-Proofing**: Can change internal IDs without breaking external APIs
|
|
245
|
+
|
|
246
|
+
Inspired by Stripe's API design and the [hashid-rails](https://github.com/jcypret/hashid-rails) gem, as well as my time at Hack Club which used a base version of this extensively.
|
|
247
|
+
|
|
248
|
+
## License
|
|
249
|
+
|
|
250
|
+
MIT
|
|
251
|
+
|
|
252
|
+
## Contributing
|
|
253
|
+
|
|
254
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jaspermayone/encoded_ids
|
data/Rakefile
ADDED
data/encoded_ids.gemspec
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/encoded_ids/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "encoded_ids"
|
|
7
|
+
spec.version = EncodedIds::VERSION
|
|
8
|
+
spec.authors = ["Jasper Mayone"]
|
|
9
|
+
spec.email = ["me@jaspermayone.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Stripe-like public IDs for Rails models"
|
|
12
|
+
spec.description = "Provides Stripe-style public identifiers (like usr_abc123) for Rails models using hashids or base62 encoding. Supports both integer and UUID primary keys, with automatic URL parameter handling and controller helpers."
|
|
13
|
+
spec.homepage = "https://github.com/jaspermayone/encoded_ids"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 3.0.0"
|
|
16
|
+
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
19
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
20
|
+
|
|
21
|
+
spec.files = Dir.chdir(__dir__) do
|
|
22
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
23
|
+
(File.expand_path(f) == __FILE__) ||
|
|
24
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
spec.bindir = "exe"
|
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
29
|
+
spec.require_paths = ["lib"]
|
|
30
|
+
|
|
31
|
+
spec.add_dependency "rails", ">= 6.1"
|
|
32
|
+
spec.add_dependency "hashid-rails", "~> 1.0"
|
|
33
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Example controller usage
|
|
4
|
+
|
|
5
|
+
class UsersController < ApplicationController
|
|
6
|
+
# Standard Rails controller actions work automatically
|
|
7
|
+
# because to_param is overridden to use public_id
|
|
8
|
+
|
|
9
|
+
def show
|
|
10
|
+
# Method 1: Use the overridden find method
|
|
11
|
+
# Accepts both internal ID and public_id
|
|
12
|
+
@user = User.find(params[:id])
|
|
13
|
+
|
|
14
|
+
# Method 2: Explicitly use find_by_public_id
|
|
15
|
+
# @user = User.find_by_public_id(params[:id])
|
|
16
|
+
|
|
17
|
+
# Method 3: Use the controller helper (accepts both formats)
|
|
18
|
+
# @user = find_by_any_id!(User, params[:id])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def update
|
|
22
|
+
@user = User.find(params[:id])
|
|
23
|
+
|
|
24
|
+
if @user.update(user_params)
|
|
25
|
+
# Automatically redirects to /users/:public_id
|
|
26
|
+
redirect_to @user, notice: "User updated"
|
|
27
|
+
else
|
|
28
|
+
render :edit
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def user_params
|
|
35
|
+
params.require(:user).permit(:name, :email)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# API controller example
|
|
40
|
+
class Api::V1::UsersController < Api::V1::BaseController
|
|
41
|
+
def show
|
|
42
|
+
# Controller helper is great for APIs
|
|
43
|
+
@user = find_by_any_id!(User, params[:id])
|
|
44
|
+
render json: @user
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def lookup
|
|
48
|
+
# Can mix internal and public IDs in the same endpoint
|
|
49
|
+
users = params[:ids].map { |id| find_by_any_id(User, id) }.compact
|
|
50
|
+
render json: users
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Example configuration for config/initializers/encoded_ids.rb
|
|
4
|
+
#
|
|
5
|
+
# Copy this file to your Rails app's config/initializers/ directory
|
|
6
|
+
# and customize as needed.
|
|
7
|
+
|
|
8
|
+
EncodedIds.configure do |config|
|
|
9
|
+
# Hashid configuration (for integer IDs)
|
|
10
|
+
# IMPORTANT: Set a unique salt in production via credentials
|
|
11
|
+
# rails credentials:edit
|
|
12
|
+
# Add: hashid: { salt: "your-unique-salt-here" }
|
|
13
|
+
config.hashid_salt = Rails.application.credentials.dig(:hashid, :salt)
|
|
14
|
+
config.hashid_min_length = 8
|
|
15
|
+
config.hashid_alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
16
|
+
|
|
17
|
+
# Base62 alphabet (for UUID encoding)
|
|
18
|
+
# Standard base62 includes both uppercase and lowercase
|
|
19
|
+
config.base62_alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
20
|
+
|
|
21
|
+
# Separator between prefix and hash
|
|
22
|
+
# Default is "_" for Stripe-style IDs like "usr_abc123"
|
|
23
|
+
# You could use "-" for "usr-abc123" or any other character
|
|
24
|
+
config.separator = "_"
|
|
25
|
+
|
|
26
|
+
# Whether to include prefix in to_param URLs
|
|
27
|
+
# false (default) = /users/k5qx9z (cleaner)
|
|
28
|
+
# true = /users/usr_k5qx9z (Stripe style - redundant with route path)
|
|
29
|
+
config.use_prefix_in_routes = false
|
|
30
|
+
end
|
data/examples/models.rb
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Example model configurations
|
|
4
|
+
|
|
5
|
+
# Integer primary key with simple prefix
|
|
6
|
+
class User < ApplicationRecord
|
|
7
|
+
include EncodedIds::HashidEncodedIds
|
|
8
|
+
set_public_id_prefix :usr
|
|
9
|
+
end
|
|
10
|
+
# user.public_id => "usr_k5qx9z"
|
|
11
|
+
# user.to_param => "k5qx9z" (no prefix in URLs by default)
|
|
12
|
+
|
|
13
|
+
# Integer primary key with longer hash for high-volume tables
|
|
14
|
+
class Event < ApplicationRecord
|
|
15
|
+
include EncodedIds::HashidEncodedIds
|
|
16
|
+
set_public_id_prefix :evt, min_hash_length: 12
|
|
17
|
+
end
|
|
18
|
+
# event.public_id => "evt_x5qp9z2m8n4k"
|
|
19
|
+
|
|
20
|
+
# Integer primary key with compositional prefix
|
|
21
|
+
class Intel::Tool::PhoneNumber < ApplicationRecord
|
|
22
|
+
include EncodedIds::HashidEncodedIds
|
|
23
|
+
add_public_id_segment :int
|
|
24
|
+
add_public_id_segment :tool
|
|
25
|
+
add_public_id_segment :phn
|
|
26
|
+
end
|
|
27
|
+
# phone.public_id => "int_tool_phn_k5qx9z"
|
|
28
|
+
|
|
29
|
+
# UUID primary key
|
|
30
|
+
class Organization < ApplicationRecord
|
|
31
|
+
include EncodedIds::UuidEncodedIds
|
|
32
|
+
set_public_id_prefix "org"
|
|
33
|
+
end
|
|
34
|
+
# org.public_id => "org_4k8xJm2pN9qW"
|
|
35
|
+
|
|
36
|
+
# UUID primary key with different prefix
|
|
37
|
+
class Team < ApplicationRecord
|
|
38
|
+
include EncodedIds::UuidEncodedIds
|
|
39
|
+
set_public_id_prefix "team"
|
|
40
|
+
end
|
|
41
|
+
# team.public_id => "team_7n2kLp4xMq8R"
|
|
42
|
+
|
|
43
|
+
# Override per-model to include prefix in routes (Stripe style)
|
|
44
|
+
class ApiKey < ApplicationRecord
|
|
45
|
+
include EncodedIds::HashidEncodedIds
|
|
46
|
+
set_public_id_prefix :key, use_prefix_in_routes: true
|
|
47
|
+
end
|
|
48
|
+
# api_key.public_id => "key_abc123"
|
|
49
|
+
# api_key.to_param => "key_abc123" (includes prefix)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EncodedIds
|
|
4
|
+
class Configuration
|
|
5
|
+
# Hashid configuration
|
|
6
|
+
attr_accessor :hashid_salt, :hashid_min_length, :hashid_alphabet
|
|
7
|
+
|
|
8
|
+
# Base62 alphabet for UUID encoding
|
|
9
|
+
attr_accessor :base62_alphabet
|
|
10
|
+
|
|
11
|
+
# Separator between prefix and hash
|
|
12
|
+
attr_accessor :separator
|
|
13
|
+
|
|
14
|
+
# Whether to include the prefix in to_param URLs
|
|
15
|
+
# true = /users/usr_k5qx9z (Stripe style)
|
|
16
|
+
# false = /users/k5qx9z (cleaner)
|
|
17
|
+
attr_accessor :use_prefix_in_routes
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@hashid_salt = nil # Will fall back to Rails.application.secret_key_base
|
|
21
|
+
@hashid_min_length = 8
|
|
22
|
+
@hashid_alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
23
|
+
@base62_alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
24
|
+
@separator = "_"
|
|
25
|
+
@use_prefix_in_routes = false
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EncodedIds
|
|
4
|
+
# Controller helpers for looking up records by either internal ID or public_id
|
|
5
|
+
#
|
|
6
|
+
# Automatically included in all controllers via Railtie
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# # Find or return nil
|
|
10
|
+
# user = find_by_any_id(User, params[:user_id])
|
|
11
|
+
#
|
|
12
|
+
# # Find or raise RecordNotFound
|
|
13
|
+
# user = find_by_any_id!(User, params[:user_id])
|
|
14
|
+
#
|
|
15
|
+
module ControllerHelpers
|
|
16
|
+
extend ActiveSupport::Concern
|
|
17
|
+
|
|
18
|
+
# Find a record by internal ID, public_id (with prefix), or hashid/encoded UUID (without prefix)
|
|
19
|
+
def find_by_any_id(model_class, id)
|
|
20
|
+
return nil if id.blank?
|
|
21
|
+
|
|
22
|
+
# If it contains the separator, it's a full public_id with prefix
|
|
23
|
+
if id.to_s.include?(EncodedIds.configuration.separator)
|
|
24
|
+
model_class.find_by_public_id(id)
|
|
25
|
+
else
|
|
26
|
+
# Use the model's find method which handles both hashids and regular IDs
|
|
27
|
+
model_class.find(id)
|
|
28
|
+
end
|
|
29
|
+
rescue ActiveRecord::RecordNotFound
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Find a record by either internal ID or public_id, raising if not found
|
|
34
|
+
def find_by_any_id!(model_class, id)
|
|
35
|
+
result = find_by_any_id(model_class, id)
|
|
36
|
+
raise ActiveRecord::RecordNotFound.new(nil, model_class.name) if result.nil?
|
|
37
|
+
|
|
38
|
+
result
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EncodedIds
|
|
4
|
+
# HashidIdentifiable for models with integer primary keys using hashids
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# class User < ApplicationRecord
|
|
8
|
+
# include EncodedIds::HashidIdentifiable
|
|
9
|
+
# set_public_id_prefix :usr
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# user = User.first
|
|
13
|
+
# user.public_id # => "usr_k5qx9z"
|
|
14
|
+
# User.find_by_public_id("usr_k5qx9z") # => <User id: 1>
|
|
15
|
+
#
|
|
16
|
+
# Compositional prefixes:
|
|
17
|
+
# class Intel::Tool::PhoneNumber < ApplicationRecord
|
|
18
|
+
# include EncodedIds::HashidIdentifiable
|
|
19
|
+
# add_public_id_segment :int
|
|
20
|
+
# add_public_id_segment :tool
|
|
21
|
+
# add_public_id_segment :phn
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# phone.public_id # => "int_tool_phn_k5qx9z"
|
|
25
|
+
#
|
|
26
|
+
module HashidIdentifiable
|
|
27
|
+
extend ActiveSupport::Concern
|
|
28
|
+
|
|
29
|
+
included do
|
|
30
|
+
include Hashid::Rails
|
|
31
|
+
class_attribute :public_id_prefix
|
|
32
|
+
class_attribute :public_id_segments, default: []
|
|
33
|
+
class_attribute :hashid_min_length, default: 8
|
|
34
|
+
class_attribute :use_prefix_in_routes, default: nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def public_id
|
|
38
|
+
"#{self.class.get_public_id_prefix}#{separator}#{hashid}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Override to_param for Rails URL generation
|
|
42
|
+
# Respects use_prefix_in_routes configuration
|
|
43
|
+
def to_param
|
|
44
|
+
use_prefix = self.class.use_prefix_in_routes.nil? ?
|
|
45
|
+
EncodedIds.configuration.use_prefix_in_routes :
|
|
46
|
+
self.class.use_prefix_in_routes
|
|
47
|
+
|
|
48
|
+
use_prefix ? public_id : hashid
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def separator
|
|
52
|
+
EncodedIds.configuration.separator
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
module ClassMethods
|
|
56
|
+
# Simple approach: set the full prefix directly
|
|
57
|
+
def set_public_id_prefix(prefix, min_hash_length: 8, use_prefix_in_routes: nil)
|
|
58
|
+
self.public_id_prefix = prefix.to_s.downcase
|
|
59
|
+
self.hashid_min_length = min_hash_length
|
|
60
|
+
self.use_prefix_in_routes = use_prefix_in_routes
|
|
61
|
+
|
|
62
|
+
# Configure hashid-rails for this model with custom length
|
|
63
|
+
hashid_config(min_hash_length: min_hash_length)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Compositional approach: add segments that get joined with underscores
|
|
67
|
+
def add_public_id_segment(segment)
|
|
68
|
+
self.public_id_segments = public_id_segments + [segment.to_s.downcase]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Find by public_id, hashid, or regular id
|
|
72
|
+
def find(*args)
|
|
73
|
+
id = args.first
|
|
74
|
+
|
|
75
|
+
# If it's a public_id string (with prefix), find by public_id
|
|
76
|
+
if id.is_a?(String) && id.include?(EncodedIds.configuration.separator)
|
|
77
|
+
find_by_public_id!(id)
|
|
78
|
+
# If it's a string (just the hash without prefix), try finding by hashid
|
|
79
|
+
elsif id.is_a?(String)
|
|
80
|
+
record = find_by_hashid(id)
|
|
81
|
+
return record if record
|
|
82
|
+
# Fall back to regular find in case it's actually an integer ID passed as string
|
|
83
|
+
super
|
|
84
|
+
else
|
|
85
|
+
super
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def find_by_public_id(id)
|
|
90
|
+
return nil unless id.is_a?(String)
|
|
91
|
+
|
|
92
|
+
parts = id.split(EncodedIds.configuration.separator)
|
|
93
|
+
hash = parts.pop # last part is always the hash
|
|
94
|
+
prefix = parts.join(EncodedIds.configuration.separator)
|
|
95
|
+
|
|
96
|
+
return nil unless prefix == get_public_id_prefix
|
|
97
|
+
|
|
98
|
+
find_by_hashid(hash)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def find_by_public_id!(id)
|
|
102
|
+
obj = find_by_public_id(id)
|
|
103
|
+
raise ActiveRecord::RecordNotFound.new(nil, name) if obj.nil?
|
|
104
|
+
|
|
105
|
+
obj
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def get_public_id_prefix
|
|
109
|
+
# Segments take precedence if defined
|
|
110
|
+
return public_id_segments.join(EncodedIds.configuration.separator) if public_id_segments.present?
|
|
111
|
+
return public_id_prefix.to_s.downcase if public_id_prefix.present?
|
|
112
|
+
|
|
113
|
+
raise NotImplementedError, "The #{name} model includes #{self.class.name}, but no prefix has been set. Use set_public_id_prefix or add_public_id_segment."
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EncodedIds
|
|
4
|
+
class Railtie < ::Rails::Railtie
|
|
5
|
+
initializer "encoded_ids.configure_hashid_rails" do
|
|
6
|
+
# Configure hashid-rails with gem settings
|
|
7
|
+
Hashid::Rails.configure do |config|
|
|
8
|
+
config.salt = EncodedIds.configuration.hashid_salt ||
|
|
9
|
+
Rails.application.credentials.dig(:hashid, :salt) ||
|
|
10
|
+
Rails.application.secret_key_base
|
|
11
|
+
config.min_hash_length = EncodedIds.configuration.hashid_min_length
|
|
12
|
+
config.alphabet = EncodedIds.configuration.hashid_alphabet
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
initializer "encoded_ids.include_controller_helpers" do
|
|
17
|
+
ActiveSupport.on_load(:action_controller) do
|
|
18
|
+
include EncodedIds::ControllerHelpers
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EncodedIds
|
|
4
|
+
# UuidIdentifiable for models with UUID primary keys using base62 encoding
|
|
5
|
+
#
|
|
6
|
+
# Since UUIDs are already non-sequential and random, this focuses on:
|
|
7
|
+
# 1. Adding a type prefix for easy identification
|
|
8
|
+
# 2. Shortening the ID for better URL/API ergonomics
|
|
9
|
+
# 3. Maintaining bi-directional conversion (public_id <-> UUID)
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# class User < ApplicationRecord
|
|
13
|
+
# include EncodedIds::UuidIdentifiable
|
|
14
|
+
# set_public_id_prefix "usr"
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# user.public_id # => "usr_4k8xJm2pN9qW"
|
|
18
|
+
# User.find_by_public_id("usr_4k8xJm2pN9qW") # => User instance
|
|
19
|
+
#
|
|
20
|
+
module UuidIdentifiable
|
|
21
|
+
extend ActiveSupport::Concern
|
|
22
|
+
|
|
23
|
+
included do
|
|
24
|
+
class_attribute :public_id_prefix, default: nil
|
|
25
|
+
class_attribute :use_prefix_in_routes, default: nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the full public ID with prefix
|
|
29
|
+
def public_id
|
|
30
|
+
prefix = self.class.get_public_id_prefix
|
|
31
|
+
encoded = self.class.encode_uuid(id)
|
|
32
|
+
"#{prefix}#{separator}#{encoded}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns just the encoded UUID (without prefix) for use in URLs
|
|
36
|
+
def encoded_id
|
|
37
|
+
self.class.encode_uuid(id)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Override to_param for Rails URL generation
|
|
41
|
+
# Respects use_prefix_in_routes configuration
|
|
42
|
+
def to_param
|
|
43
|
+
use_prefix = self.class.use_prefix_in_routes.nil? ?
|
|
44
|
+
EncodedIds.configuration.use_prefix_in_routes :
|
|
45
|
+
self.class.use_prefix_in_routes
|
|
46
|
+
|
|
47
|
+
use_prefix ? public_id : encoded_id
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def separator
|
|
51
|
+
EncodedIds.configuration.separator
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Alias for Rails conventions
|
|
55
|
+
alias_method :to_public_param, :public_id
|
|
56
|
+
|
|
57
|
+
class_methods do
|
|
58
|
+
# Set the prefix for this model's public IDs
|
|
59
|
+
def set_public_id_prefix(prefix, use_prefix_in_routes: nil)
|
|
60
|
+
self.public_id_prefix = prefix.to_s.freeze
|
|
61
|
+
self.use_prefix_in_routes = use_prefix_in_routes
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Get the configured prefix, with validation
|
|
65
|
+
def get_public_id_prefix
|
|
66
|
+
raise "Public ID prefix not set for #{name}. Call set_public_id_prefix in your model." if public_id_prefix.blank?
|
|
67
|
+
public_id_prefix
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Find by public_id, encoded UUID, or regular UUID
|
|
71
|
+
def find(*args)
|
|
72
|
+
id = args.first
|
|
73
|
+
|
|
74
|
+
# If it's a public_id string (with prefix), find by public_id
|
|
75
|
+
if id.is_a?(String) && id.include?(EncodedIds.configuration.separator)
|
|
76
|
+
find_by_public_id!(id)
|
|
77
|
+
# If it's a string that looks like an encoded UUID (not a standard UUID format)
|
|
78
|
+
elsif id.is_a?(String) && !id.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i)
|
|
79
|
+
uuid = decode_to_uuid(id)
|
|
80
|
+
return find_by(id: uuid) if uuid
|
|
81
|
+
# Fall back to regular find
|
|
82
|
+
super
|
|
83
|
+
else
|
|
84
|
+
super
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Find a record by its public ID
|
|
89
|
+
def find_by_public_id(public_id)
|
|
90
|
+
return nil if public_id.blank?
|
|
91
|
+
|
|
92
|
+
prefix = get_public_id_prefix
|
|
93
|
+
separator = EncodedIds.configuration.separator
|
|
94
|
+
return nil unless public_id.to_s.start_with?("#{prefix}#{separator}")
|
|
95
|
+
|
|
96
|
+
encoded = public_id.to_s.sub("#{prefix}#{separator}", "")
|
|
97
|
+
uuid = decode_to_uuid(encoded)
|
|
98
|
+
return nil unless uuid
|
|
99
|
+
|
|
100
|
+
find_by(id: uuid)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Find a record by its public ID, raising RecordNotFound if not found
|
|
104
|
+
def find_by_public_id!(public_id)
|
|
105
|
+
find_by_public_id(public_id) || raise(ActiveRecord::RecordNotFound, "Couldn't find #{name} with public_id=#{public_id}")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Check if a string looks like a valid public ID for this model
|
|
109
|
+
def valid_public_id?(public_id)
|
|
110
|
+
return false if public_id.blank?
|
|
111
|
+
separator = EncodedIds.configuration.separator
|
|
112
|
+
public_id.to_s.start_with?("#{get_public_id_prefix}#{separator}")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Encode a UUID to a shorter base62 string
|
|
116
|
+
def encode_uuid(uuid)
|
|
117
|
+
return nil if uuid.blank?
|
|
118
|
+
|
|
119
|
+
alphabet = EncodedIds.configuration.base62_alphabet
|
|
120
|
+
|
|
121
|
+
# Remove hyphens and convert to integer
|
|
122
|
+
hex = uuid.to_s.delete("-")
|
|
123
|
+
num = hex.to_i(16)
|
|
124
|
+
|
|
125
|
+
# Convert to base62
|
|
126
|
+
return "0" if num.zero?
|
|
127
|
+
|
|
128
|
+
result = ""
|
|
129
|
+
while num > 0
|
|
130
|
+
result = alphabet[num % 62] + result
|
|
131
|
+
num /= 62
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
result
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Decode a base62 string back to UUID format
|
|
138
|
+
def decode_to_uuid(encoded)
|
|
139
|
+
return nil if encoded.blank?
|
|
140
|
+
|
|
141
|
+
alphabet = EncodedIds.configuration.base62_alphabet
|
|
142
|
+
|
|
143
|
+
# Convert from base62 to integer
|
|
144
|
+
num = 0
|
|
145
|
+
encoded.each_char do |char|
|
|
146
|
+
index = alphabet.index(char)
|
|
147
|
+
return nil if index.nil? # Invalid character
|
|
148
|
+
num = num * 62 + index
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Convert to hex and format as UUID
|
|
152
|
+
hex = num.to_s(16).rjust(32, "0")
|
|
153
|
+
return nil if hex.length > 32 # Overflow protection
|
|
154
|
+
|
|
155
|
+
# Format as UUID: 8-4-4-4-12
|
|
156
|
+
"#{hex[0..7]}-#{hex[8..11]}-#{hex[12..15]}-#{hex[16..19]}-#{hex[20..31]}"
|
|
157
|
+
rescue StandardError
|
|
158
|
+
nil
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
data/lib/encoded_ids.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hashid/rails"
|
|
4
|
+
require_relative "encoded_ids/version"
|
|
5
|
+
require_relative "encoded_ids/configuration"
|
|
6
|
+
require_relative "encoded_ids/hashid_identifiable"
|
|
7
|
+
require_relative "encoded_ids/uuid_identifiable"
|
|
8
|
+
require_relative "encoded_ids/controller_helpers"
|
|
9
|
+
require_relative "encoded_ids/railtie" if defined?(Rails::Railtie)
|
|
10
|
+
|
|
11
|
+
module EncodedIds
|
|
12
|
+
class << self
|
|
13
|
+
attr_writer :configuration
|
|
14
|
+
|
|
15
|
+
def configuration
|
|
16
|
+
@configuration ||= Configuration.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def configure
|
|
20
|
+
yield(configuration)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def reset_configuration!
|
|
24
|
+
@configuration = Configuration.new
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: encoded_ids
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jasper Mayone
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '6.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: hashid-rails
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
40
|
+
description: Provides Stripe-style public identifiers (like usr_abc123) for Rails
|
|
41
|
+
models using hashids or base62 encoding. Supports both integer and UUID primary
|
|
42
|
+
keys, with automatic URL parameter handling and controller helpers.
|
|
43
|
+
email:
|
|
44
|
+
- me@jaspermayone.com
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- ".rspec"
|
|
50
|
+
- CHANGELOG.md
|
|
51
|
+
- LICENSE.md
|
|
52
|
+
- README.md
|
|
53
|
+
- Rakefile
|
|
54
|
+
- encoded_ids.gemspec
|
|
55
|
+
- examples/controller.rb
|
|
56
|
+
- examples/initializer.rb
|
|
57
|
+
- examples/models.rb
|
|
58
|
+
- lib/encoded_ids.rb
|
|
59
|
+
- lib/encoded_ids/configuration.rb
|
|
60
|
+
- lib/encoded_ids/controller_helpers.rb
|
|
61
|
+
- lib/encoded_ids/hashid_identifiable.rb
|
|
62
|
+
- lib/encoded_ids/railtie.rb
|
|
63
|
+
- lib/encoded_ids/uuid_identifiable.rb
|
|
64
|
+
- lib/encoded_ids/version.rb
|
|
65
|
+
homepage: https://github.com/jaspermayone/encoded_ids
|
|
66
|
+
licenses:
|
|
67
|
+
- MIT
|
|
68
|
+
metadata:
|
|
69
|
+
homepage_uri: https://github.com/jaspermayone/encoded_ids
|
|
70
|
+
source_code_uri: https://github.com/jaspermayone/encoded_ids
|
|
71
|
+
changelog_uri: https://github.com/jaspermayone/encoded_ids/blob/main/CHANGELOG.md
|
|
72
|
+
rdoc_options: []
|
|
73
|
+
require_paths:
|
|
74
|
+
- lib
|
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: 3.0.0
|
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '0'
|
|
85
|
+
requirements: []
|
|
86
|
+
rubygems_version: 3.6.9
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: Stripe-like public IDs for Rails models
|
|
89
|
+
test_files: []
|