encoded_id-rails 1.0.0.rc6 → 1.0.0.rc7
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/CHANGELOG.md +24 -4
- data/README.md +44 -33
- data/context/encoded_id-rails.md +298 -80
- data/context/encoded_id.md +229 -75
- data/lib/encoded_id/rails/active_record_finders.rb +4 -2
- data/lib/encoded_id/rails/annotated_id.rb +11 -14
- data/lib/encoded_id/rails/annotated_id_parser.rb +1 -0
- data/lib/encoded_id/rails/coder.rb +45 -18
- data/lib/encoded_id/rails/composite_id_base.rb +39 -0
- data/lib/encoded_id/rails/configuration.rb +28 -12
- data/lib/encoded_id/rails/encoder_methods.rb +24 -9
- data/lib/encoded_id/rails/finder_methods.rb +1 -0
- data/lib/encoded_id/rails/model.rb +35 -5
- data/lib/encoded_id/rails/path_param.rb +1 -0
- data/lib/encoded_id/rails/persists.rb +7 -5
- data/lib/encoded_id/rails/query_methods.rb +1 -0
- data/lib/encoded_id/rails/salt.rb +1 -0
- data/lib/encoded_id/rails/slugged_id.rb +11 -14
- data/lib/encoded_id/rails/slugged_id_parser.rb +1 -0
- data/lib/encoded_id/rails/slugged_path_param.rb +1 -0
- data/lib/encoded_id/rails.rb +2 -0
- data/lib/generators/encoded_id/rails/install_generator.rb +36 -2
- data/lib/generators/encoded_id/rails/templates/hashids_encoded_id.rb +122 -0
- data/lib/generators/encoded_id/rails/templates/{encoded_id.rb → sqids_encoded_id.rb} +29 -11
- metadata +7 -5
data/context/encoded_id-rails.md
CHANGED
|
@@ -11,9 +11,20 @@
|
|
|
11
11
|
- **Slugged IDs**: Human-readable slugs combined with encoded IDs (e.g., `john-doe--user_p5w9-z27j`)
|
|
12
12
|
- **Annotated IDs**: Model type prefixes for clarity (e.g., `user_p5w9-z27j`)
|
|
13
13
|
- **Finder Methods**: Find records using encoded IDs with familiar ActiveRecord syntax
|
|
14
|
-
- **Database Persistence**: Optional storage of encoded IDs for performance
|
|
15
|
-
- **Per-Model Configuration**: Different encoding strategies per model
|
|
14
|
+
- **Database Persistence**: Optional storage of encoded IDs for performance with automatic validations
|
|
15
|
+
- **Per-Model Configuration**: Different encoding strategies per model with inheritance support
|
|
16
16
|
- **ActiveRecord Finder Overrides**: Seamless integration with `find`, `find_by_id`, etc.
|
|
17
|
+
- **Record Duplication Safety**: Automatic encoded ID cache clearing on `dup`
|
|
18
|
+
|
|
19
|
+
## Quick Module Reference
|
|
20
|
+
|
|
21
|
+
| Module | Purpose | Key Method |
|
|
22
|
+
|--------|---------|------------|
|
|
23
|
+
| `EncodedId::Rails::Model` | Core functionality | `#encoded_id` |
|
|
24
|
+
| `EncodedId::Rails::PathParam` | URLs use encoded IDs | `#to_param` |
|
|
25
|
+
| `EncodedId::Rails::SluggedPathParam` | URLs use slugged IDs | `#to_param` |
|
|
26
|
+
| `EncodedId::Rails::ActiveRecordFinders` | Transparent finder overrides | `find()` |
|
|
27
|
+
| `EncodedId::Rails::Persists` | Database persistence with validations | `set_normalized_encoded_id!` |
|
|
17
28
|
|
|
18
29
|
## Installation & Setup
|
|
19
30
|
|
|
@@ -24,11 +35,18 @@ gem 'encoded_id-rails'
|
|
|
24
35
|
# Install
|
|
25
36
|
bundle install
|
|
26
37
|
|
|
27
|
-
# Generate configuration
|
|
38
|
+
# Generate configuration (prompts for encoder choice: sqids or hashids)
|
|
28
39
|
rails generate encoded_id:rails:install
|
|
40
|
+
|
|
41
|
+
# Or specify encoder directly
|
|
42
|
+
rails generate encoded_id:rails:install --encoder=sqids
|
|
43
|
+
# or
|
|
44
|
+
rails generate encoded_id:rails:install --encoder=hashids
|
|
29
45
|
```
|
|
30
46
|
|
|
31
|
-
|
|
47
|
+
The generator creates `config/initializers/encoded_id.rb` with encoder-specific configuration:
|
|
48
|
+
- **Sqids**: No salt required, ready to use
|
|
49
|
+
- **Hashids**: Requires salt configuration (generator includes commented template)
|
|
32
50
|
|
|
33
51
|
## Core Modules
|
|
34
52
|
|
|
@@ -58,7 +76,7 @@ Returns encoded ID with human-readable slug.
|
|
|
58
76
|
```ruby
|
|
59
77
|
class User < ApplicationRecord
|
|
60
78
|
include EncodedId::Rails::Model
|
|
61
|
-
|
|
79
|
+
|
|
62
80
|
def name_for_encoded_id_slug
|
|
63
81
|
full_name.parameterize
|
|
64
82
|
end
|
|
@@ -68,7 +86,7 @@ user.slugged_encoded_id # => "john-doe--user_p5w9-z27j"
|
|
|
68
86
|
```
|
|
69
87
|
|
|
70
88
|
##### annotation_for_encoded_id
|
|
71
|
-
Override to customize the annotation prefix.
|
|
89
|
+
Override to customize the annotation prefix (defaults to `model_name.underscore`).
|
|
72
90
|
|
|
73
91
|
```ruby
|
|
74
92
|
def annotation_for_encoded_id
|
|
@@ -76,6 +94,13 @@ def annotation_for_encoded_id
|
|
|
76
94
|
end
|
|
77
95
|
```
|
|
78
96
|
|
|
97
|
+
##### clear_encoded_id_cache!
|
|
98
|
+
Manually clear memoized encoded ID values. Called automatically on `reload` and `dup`.
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
user.clear_encoded_id_cache!
|
|
102
|
+
```
|
|
103
|
+
|
|
79
104
|
#### Class Methods
|
|
80
105
|
|
|
81
106
|
##### find_by_encoded_id(encoded_id)
|
|
@@ -90,8 +115,13 @@ User.find_by_encoded_id("john-doe--user_p5w9-z27j") # Slugged
|
|
|
90
115
|
##### find_by_encoded_id!(encoded_id)
|
|
91
116
|
Same as above but raises `ActiveRecord::RecordNotFound` if not found.
|
|
92
117
|
|
|
118
|
+
```ruby
|
|
119
|
+
User.find_by_encoded_id!("user_p5w9-z27j")
|
|
120
|
+
# Raises ActiveRecord::RecordNotFound if not found
|
|
121
|
+
```
|
|
122
|
+
|
|
93
123
|
##### find_all_by_encoded_id(encoded_id)
|
|
94
|
-
Find multiple records when encoded ID contains multiple IDs.
|
|
124
|
+
Find multiple records when encoded ID contains multiple IDs (returns nil if none found).
|
|
95
125
|
|
|
96
126
|
```ruby
|
|
97
127
|
# If encoded ID represents [78, 45]
|
|
@@ -99,18 +129,37 @@ User.find_all_by_encoded_id("z2j7-0dmw")
|
|
|
99
129
|
# => [#<User id: 78>, #<User id: 45>]
|
|
100
130
|
```
|
|
101
131
|
|
|
102
|
-
#####
|
|
103
|
-
|
|
132
|
+
##### find_all_by_encoded_id!(encoded_id)
|
|
133
|
+
Same as above but raises `ActiveRecord::RecordNotFound` if:
|
|
134
|
+
- No records found
|
|
135
|
+
- Number of records doesn't match number of decoded IDs
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
User.find_all_by_encoded_id!("z2j7-0dmw")
|
|
139
|
+
# Raises if records not found or count mismatch
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
##### where_encoded_id(*encoded_ids)
|
|
143
|
+
Returns ActiveRecord relation for chaining. Can take multiple IDs.
|
|
104
144
|
|
|
105
145
|
```ruby
|
|
106
146
|
User.where_encoded_id("user_p5w9-z27j").where(active: true)
|
|
147
|
+
User.where_encoded_id("id1", "id2", "id3")
|
|
107
148
|
```
|
|
108
149
|
|
|
109
|
-
##### encode_encoded_id(id)
|
|
110
|
-
Encode a specific ID using model's configuration.
|
|
150
|
+
##### encode_encoded_id(id, **options)
|
|
151
|
+
Encode a specific ID using model's configuration (optionally override options).
|
|
111
152
|
|
|
112
153
|
```ruby
|
|
113
154
|
User.encode_encoded_id(123) # => "p5w9-z27j"
|
|
155
|
+
User.encode_encoded_id(123, id_length: 12) # Override length
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
##### decode_encoded_id(encoded_id)
|
|
159
|
+
Decode an encoded ID using model's configuration.
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
User.decode_encoded_id("user_p5w9-z27j") # => [123]
|
|
114
163
|
```
|
|
115
164
|
|
|
116
165
|
### EncodedId::Rails::PathParam
|
|
@@ -137,7 +186,7 @@ Uses slugged encoded IDs in URLs.
|
|
|
137
186
|
class User < ApplicationRecord
|
|
138
187
|
include EncodedId::Rails::Model
|
|
139
188
|
include EncodedId::Rails::SluggedPathParam
|
|
140
|
-
|
|
189
|
+
|
|
141
190
|
def name_for_encoded_id_slug
|
|
142
191
|
full_name.parameterize
|
|
143
192
|
end
|
|
@@ -150,6 +199,8 @@ user.to_param # => "john-doe--user_p5w9-z27j"
|
|
|
150
199
|
|
|
151
200
|
Overrides standard ActiveRecord finders to handle encoded IDs transparently.
|
|
152
201
|
|
|
202
|
+
**IMPORTANT**: Only use with integer primary keys. Do NOT use with string-based primary keys (UUIDs).
|
|
203
|
+
|
|
153
204
|
```ruby
|
|
154
205
|
class Product < ApplicationRecord
|
|
155
206
|
include EncodedId::Rails::Model
|
|
@@ -170,19 +221,17 @@ def show
|
|
|
170
221
|
end
|
|
171
222
|
```
|
|
172
223
|
|
|
173
|
-
**Warning**: Do NOT use with string-based primary keys (UUIDs).
|
|
174
|
-
|
|
175
224
|
### EncodedId::Rails::Persists
|
|
176
225
|
|
|
177
|
-
Stores encoded IDs in database for performance.
|
|
226
|
+
Stores encoded IDs in database for performance with automatic validations.
|
|
178
227
|
|
|
179
228
|
```bash
|
|
180
229
|
# Generate migration
|
|
181
230
|
rails generate encoded_id:rails:add_columns User
|
|
182
231
|
|
|
183
|
-
#
|
|
184
|
-
# - normalized_encoded_id (string)
|
|
185
|
-
# - prefixed_encoded_id (string)
|
|
232
|
+
# Creates migration adding:
|
|
233
|
+
# - normalized_encoded_id (string) - for lookups without separators/annotations
|
|
234
|
+
# - prefixed_encoded_id (string) - with annotation prefix
|
|
186
235
|
```
|
|
187
236
|
|
|
188
237
|
```ruby
|
|
@@ -190,12 +239,53 @@ class User < ApplicationRecord
|
|
|
190
239
|
include EncodedId::Rails::Model
|
|
191
240
|
include EncodedId::Rails::Persists
|
|
192
241
|
end
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Automatic Validations**:
|
|
245
|
+
- Uniqueness validation on `normalized_encoded_id`
|
|
246
|
+
- Uniqueness validation on `prefixed_encoded_id`
|
|
247
|
+
- Read-only enforcement after creation (prevents manual updates)
|
|
248
|
+
|
|
249
|
+
**Callbacks**:
|
|
250
|
+
- `after_create`: Automatically sets encoded IDs
|
|
251
|
+
- `before_save`: Updates encoded IDs if ID changed
|
|
252
|
+
- `after_commit`: Validates persisted values match computed values
|
|
253
|
+
|
|
254
|
+
**Instance Methods**:
|
|
255
|
+
|
|
256
|
+
##### set_normalized_encoded_id!
|
|
257
|
+
Manually update persisted encoded IDs (uses `update_columns` to bypass callbacks).
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
user.set_normalized_encoded_id!
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
##### update_normalized_encoded_id!
|
|
264
|
+
Update persisted encoded IDs in-memory (will be saved with record).
|
|
265
|
+
|
|
266
|
+
```ruby
|
|
267
|
+
user.update_normalized_encoded_id!
|
|
268
|
+
user.save
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Database Lookups**:
|
|
193
272
|
|
|
273
|
+
```ruby
|
|
194
274
|
# Fast lookups via direct DB query
|
|
195
275
|
User.where(normalized_encoded_id: "p5w9z27j").first
|
|
276
|
+
User.where(prefixed_encoded_id: "user_p5w9-z27j").first
|
|
196
277
|
|
|
197
|
-
# Add
|
|
198
|
-
|
|
278
|
+
# IMPORTANT: Add indexes for performance
|
|
279
|
+
# See migration section below
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Record Duplication**:
|
|
283
|
+
When using `dup`, persisted encoded ID columns are automatically set to `nil` for the new record:
|
|
284
|
+
|
|
285
|
+
```ruby
|
|
286
|
+
new_user = existing_user.dup
|
|
287
|
+
new_user.normalized_encoded_id # => nil (will be set on save)
|
|
288
|
+
new_user.prefixed_encoded_id # => nil
|
|
199
289
|
```
|
|
200
290
|
|
|
201
291
|
## Configuration
|
|
@@ -205,56 +295,130 @@ add_index :users, :normalized_encoded_id, unique: true
|
|
|
205
295
|
```ruby
|
|
206
296
|
# config/initializers/encoded_id.rb
|
|
207
297
|
EncodedId::Rails.configure do |config|
|
|
208
|
-
# Required
|
|
209
|
-
config.salt = "your-secret-salt"
|
|
210
|
-
|
|
211
|
-
#
|
|
298
|
+
# Required for Hashids encoder
|
|
299
|
+
config.salt = "your-secret-salt" # Not required for Sqids
|
|
300
|
+
|
|
301
|
+
# Basic Options
|
|
212
302
|
config.id_length = 8 # Minimum length
|
|
213
303
|
config.character_group_size = 4 # Split every X chars
|
|
214
304
|
config.group_separator = "-" # Split character
|
|
215
305
|
config.alphabet = EncodedId::Alphabet.modified_crockford
|
|
306
|
+
|
|
307
|
+
# Annotation/Prefix Options
|
|
216
308
|
config.annotation_method_name = :annotation_for_encoded_id
|
|
217
309
|
config.annotated_id_separator = "_"
|
|
310
|
+
|
|
311
|
+
# Slug Options
|
|
218
312
|
config.slug_value_method_name = :name_for_encoded_id_slug
|
|
219
313
|
config.slugged_id_separator = "--"
|
|
314
|
+
|
|
315
|
+
# Encoder Selection
|
|
316
|
+
config.encoder = :sqids # Default: :sqids (or :hashids for backwards compatibility)
|
|
317
|
+
config.downcase_on_decode = false # Default: false (set to true for pre-v1 compatibility)
|
|
318
|
+
|
|
319
|
+
# Blocklist
|
|
320
|
+
config.blocklist = nil # EncodedId::Blocklist.minimal or custom
|
|
321
|
+
|
|
322
|
+
# Auto-include PathParam (makes all models use encoded IDs in URLs)
|
|
220
323
|
config.model_to_param_returns_encoded_id = false
|
|
221
|
-
config.encoder = :hashids # or :sqids
|
|
222
|
-
config.blocklist = nil
|
|
223
|
-
config.hex_digit_encoding_group_size = 4
|
|
224
324
|
end
|
|
225
325
|
```
|
|
226
326
|
|
|
327
|
+
**Note**: As of v1.0.0:
|
|
328
|
+
- Default encoder is `:sqids` (no salt required)
|
|
329
|
+
- `downcase_on_decode` defaults to `false` (case-sensitive)
|
|
330
|
+
- For backwards compatibility with pre-v1: set `encoder: :hashids` and `downcase_on_decode: true`
|
|
331
|
+
|
|
332
|
+
### Encoder-Specific Notes
|
|
333
|
+
|
|
334
|
+
**Sqids (default)**:
|
|
335
|
+
- No salt required
|
|
336
|
+
- Automatically avoids blocklisted words via iteration
|
|
337
|
+
- Faster decoding
|
|
338
|
+
|
|
339
|
+
**Hashids**:
|
|
340
|
+
- Requires salt (minimum 4 characters)
|
|
341
|
+
- Raises `EncodedId::BlocklistError` if blocklisted word appears
|
|
342
|
+
- Faster encoding, especially with blocklists
|
|
343
|
+
|
|
227
344
|
### Per-Model Configuration
|
|
228
345
|
|
|
229
|
-
|
|
346
|
+
#### Using `encoded_id_config` (Recommended)
|
|
347
|
+
|
|
348
|
+
The cleanest way to configure encoding options per model:
|
|
230
349
|
|
|
231
350
|
```ruby
|
|
232
351
|
class User < ApplicationRecord
|
|
233
352
|
include EncodedId::Rails::Model
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
end
|
|
353
|
+
|
|
354
|
+
# Configure encoder settings for this model
|
|
355
|
+
encoded_id_config encoder: :hashids, id_length: 12
|
|
238
356
|
end
|
|
239
357
|
```
|
|
240
358
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
Full control via `encoded_id_coder`:
|
|
359
|
+
**Supports all configuration options**:
|
|
244
360
|
|
|
245
361
|
```ruby
|
|
246
362
|
class Product < ApplicationRecord
|
|
247
363
|
include EncodedId::Rails::Model
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
364
|
+
|
|
365
|
+
encoded_id_config(
|
|
366
|
+
encoder: :sqids,
|
|
367
|
+
id_length: 12,
|
|
368
|
+
character_group_size: 3,
|
|
369
|
+
alphabet: EncodedId::Alphabet.new("0123456789ABCDEF"),
|
|
370
|
+
blocklist: EncodedId::Blocklist.minimal,
|
|
371
|
+
downcase_on_decode: true
|
|
372
|
+
)
|
|
373
|
+
end
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Available Options**:
|
|
377
|
+
- `encoder` - `:sqids` or `:hashids`
|
|
378
|
+
- `id_length` - Minimum encoded ID length
|
|
379
|
+
- `character_group_size` - Character grouping (nil for no grouping)
|
|
380
|
+
- `alphabet` - Custom alphabet
|
|
381
|
+
- `blocklist` - Blocklist instance or array
|
|
382
|
+
- `downcase_on_decode` - Case-insensitive decoding
|
|
383
|
+
- `annotation_method_name` - Method to call for annotation
|
|
384
|
+
- `annotated_id_separator` - Separator for annotated IDs
|
|
385
|
+
- `slug_value_method_name` - Method to call for slug
|
|
386
|
+
- `slugged_id_separator` - Separator for slugged IDs
|
|
387
|
+
|
|
388
|
+
**Note**: For advanced blocklist control (modes like `:length_threshold`, `:always`, `:raise_if_likely`), use the base `EncodedId::ReversibleId` directly or configure via custom coder.
|
|
389
|
+
|
|
390
|
+
**Configuration Inheritance**: Child classes inherit their parent's configuration:
|
|
391
|
+
|
|
392
|
+
```ruby
|
|
393
|
+
class BaseModel < ApplicationRecord
|
|
394
|
+
self.abstract_class = true
|
|
395
|
+
include EncodedId::Rails::Model
|
|
396
|
+
|
|
397
|
+
# All child models inherit these settings
|
|
398
|
+
encoded_id_config encoder: :hashids, id_length: 10
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
class User < BaseModel
|
|
402
|
+
# Inherits encoder: :hashids, id_length: 10
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
class Product < BaseModel
|
|
406
|
+
# Override parent settings
|
|
407
|
+
encoded_id_config encoder: :sqids
|
|
408
|
+
# Now uses encoder: :sqids but still id_length: 10
|
|
409
|
+
end
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
#### Custom Salt Per Model
|
|
413
|
+
|
|
414
|
+
Override salt per model (Hashids only):
|
|
415
|
+
|
|
416
|
+
```ruby
|
|
417
|
+
class User < ApplicationRecord
|
|
418
|
+
include EncodedId::Rails::Model
|
|
419
|
+
|
|
420
|
+
def self.encoded_id_salt
|
|
421
|
+
"user-specific-salt"
|
|
258
422
|
end
|
|
259
423
|
end
|
|
260
424
|
```
|
|
@@ -266,18 +430,18 @@ Different configurations for different use cases:
|
|
|
266
430
|
```ruby
|
|
267
431
|
class User < ApplicationRecord
|
|
268
432
|
include EncodedId::Rails::Model
|
|
269
|
-
|
|
433
|
+
|
|
270
434
|
# Short ID for QR codes
|
|
271
435
|
def qr_encoded_id
|
|
272
|
-
self.class.encode_encoded_id(id,
|
|
273
|
-
id_length: 6,
|
|
436
|
+
self.class.encode_encoded_id(id,
|
|
437
|
+
id_length: 6,
|
|
274
438
|
character_group_size: nil
|
|
275
439
|
)
|
|
276
440
|
end
|
|
277
|
-
|
|
441
|
+
|
|
278
442
|
# API-friendly (no separators/annotations)
|
|
279
443
|
def api_encoded_id
|
|
280
|
-
self.class.encode_encoded_id(id,
|
|
444
|
+
self.class.encode_encoded_id(id,
|
|
281
445
|
character_group_size: nil,
|
|
282
446
|
annotation_method_name: nil
|
|
283
447
|
)
|
|
@@ -318,6 +482,21 @@ class ProductsController < ApplicationController
|
|
|
318
482
|
end
|
|
319
483
|
```
|
|
320
484
|
|
|
485
|
+
### With Slugged IDs
|
|
486
|
+
|
|
487
|
+
```ruby
|
|
488
|
+
# routes.rb
|
|
489
|
+
resources :articles
|
|
490
|
+
|
|
491
|
+
# ArticlesController
|
|
492
|
+
class ArticlesController < ApplicationController
|
|
493
|
+
def show
|
|
494
|
+
# Handles slugged IDs automatically
|
|
495
|
+
@article = Article.find_by_encoded_id!(params[:id])
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
```
|
|
499
|
+
|
|
321
500
|
## Common Patterns
|
|
322
501
|
|
|
323
502
|
### Complete Integration Example
|
|
@@ -328,18 +507,16 @@ class Product < ApplicationRecord
|
|
|
328
507
|
include EncodedId::Rails::SluggedPathParam
|
|
329
508
|
include EncodedId::Rails::Persists
|
|
330
509
|
include EncodedId::Rails::ActiveRecordFinders
|
|
331
|
-
|
|
510
|
+
|
|
511
|
+
# Configure encoding options for this model
|
|
512
|
+
encoded_id_config(
|
|
513
|
+
blocklist: EncodedId::Blocklist.minimal,
|
|
514
|
+
id_length: 10
|
|
515
|
+
)
|
|
516
|
+
|
|
332
517
|
def name_for_encoded_id_slug
|
|
333
518
|
name.parameterize
|
|
334
519
|
end
|
|
335
|
-
|
|
336
|
-
def self.encoded_id_coder(options = {})
|
|
337
|
-
super(options.merge(
|
|
338
|
-
encoder: :sqids,
|
|
339
|
-
blocklist: ["offensive", "words"],
|
|
340
|
-
id_length: 10
|
|
341
|
-
))
|
|
342
|
-
end
|
|
343
520
|
end
|
|
344
521
|
|
|
345
522
|
# Usage
|
|
@@ -359,8 +536,15 @@ product_path(product) # => "/products/cool-gadget--product_k6jR8Myo23"
|
|
|
359
536
|
|
|
360
537
|
### Migration for Existing Data
|
|
361
538
|
|
|
539
|
+
When adding `Persists` module to existing models:
|
|
540
|
+
|
|
362
541
|
```ruby
|
|
363
|
-
#
|
|
542
|
+
# 1. Generate and run migration
|
|
543
|
+
rails generate encoded_id:rails:add_columns User
|
|
544
|
+
# Edit migration to add indexes (see below)
|
|
545
|
+
rails db:migrate
|
|
546
|
+
|
|
547
|
+
# 2. Backfill existing records
|
|
364
548
|
User.find_each(batch_size: 1000) do |user|
|
|
365
549
|
user.set_normalized_encoded_id!
|
|
366
550
|
end
|
|
@@ -375,12 +559,45 @@ class BackfillEncodedIdsJob < ApplicationJob
|
|
|
375
559
|
end
|
|
376
560
|
```
|
|
377
561
|
|
|
562
|
+
**Enhanced Migration with Indexes** (recommended):
|
|
563
|
+
|
|
564
|
+
```ruby
|
|
565
|
+
class AddEncodedIdColumnsToUsers < ActiveRecord::Migration[7.0]
|
|
566
|
+
def change
|
|
567
|
+
add_column :users, :normalized_encoded_id, :string
|
|
568
|
+
add_column :users, :prefixed_encoded_id, :string
|
|
569
|
+
|
|
570
|
+
# Add indexes for performance (critical for lookups)
|
|
571
|
+
add_index :users, :normalized_encoded_id, unique: true
|
|
572
|
+
add_index :users, :prefixed_encoded_id, unique: true
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### Accessing Configuration
|
|
578
|
+
|
|
579
|
+
```ruby
|
|
580
|
+
# Access global configuration
|
|
581
|
+
EncodedId::Rails.configuration.salt
|
|
582
|
+
EncodedId::Rails.configuration.encoder # => :sqids
|
|
583
|
+
EncodedId::Rails.configuration.id_length # => 8
|
|
584
|
+
|
|
585
|
+
# Access model-specific configuration
|
|
586
|
+
User.encoded_id_salt
|
|
587
|
+
User.encoded_id_coder.encoder
|
|
588
|
+
User.encoded_id_options # => Hash of configured options
|
|
589
|
+
```
|
|
590
|
+
|
|
378
591
|
## Performance Considerations
|
|
379
592
|
|
|
380
593
|
1. **Persistence**: Use `EncodedId::Rails::Persists` for high-traffic lookups
|
|
381
|
-
2. **Indexes**:
|
|
382
|
-
3. **Caching**: Encoded IDs are deterministic - cache them if needed
|
|
383
|
-
4. **Blocklists**: Large blocklists impact performance, especially with Sqids
|
|
594
|
+
2. **Indexes**: ALWAYS add database indexes on `normalized_encoded_id` and `prefixed_encoded_id`
|
|
595
|
+
3. **Caching**: Encoded IDs are deterministic and memoized per record - cache them if needed
|
|
596
|
+
4. **Blocklists**: Large blocklists impact encoding performance, especially with Sqids (iterates to avoid words)
|
|
597
|
+
5. **Blocklist Modes**:
|
|
598
|
+
- Sqids: Iteratively regenerates to avoid blocklisted words (may impact performance)
|
|
599
|
+
- Hashids: Raises exception if blocklisted word detected (requires retry logic)
|
|
600
|
+
- For advanced control, use base `EncodedId` configuration directly
|
|
384
601
|
|
|
385
602
|
## Best Practices
|
|
386
603
|
|
|
@@ -389,32 +606,30 @@ end
|
|
|
389
606
|
3. **Error Handling**: Always use `find_by_encoded_id!` in controllers for proper 404s
|
|
390
607
|
4. **URL Design**: Choose between encoded IDs vs slugged IDs based on UX needs
|
|
391
608
|
5. **Testing**: Test with both regular IDs and encoded IDs in your specs
|
|
609
|
+
6. **Indexes**: Always add database indexes when using `Persists` module
|
|
610
|
+
7. **Validations**: Rely on automatic validations from `Persists` - don't manually update columns
|
|
611
|
+
8. **Record Duplication**: `dup` automatically clears encoded IDs - persisted IDs set to nil for new records
|
|
392
612
|
|
|
393
|
-
##
|
|
394
|
-
|
|
395
|
-
### Load Order Issues
|
|
396
|
-
|
|
397
|
-
```ruby
|
|
398
|
-
# In initializer if seeing load errors
|
|
399
|
-
require 'encoded_id'
|
|
400
|
-
require 'encoded_id/rails'
|
|
401
|
-
|
|
402
|
-
# Or in ApplicationRecord
|
|
403
|
-
require 'encoded_id/rails/model'
|
|
404
|
-
require 'encoded_id/rails/path_param'
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
### Debugging
|
|
613
|
+
## Debugging
|
|
408
614
|
|
|
409
615
|
```ruby
|
|
410
616
|
# Check configuration
|
|
411
617
|
user = User.first
|
|
412
618
|
user.class.encoded_id_salt
|
|
413
619
|
user.class.encoded_id_coder.encoder
|
|
620
|
+
user.class.encoded_id_options
|
|
414
621
|
|
|
415
622
|
# Test encoding/decoding
|
|
416
623
|
encoded = User.encode_encoded_id(123)
|
|
417
624
|
decoded = User.decode_encoded_id(encoded)
|
|
625
|
+
|
|
626
|
+
# Inspect persisted values (if using Persists)
|
|
627
|
+
user.normalized_encoded_id
|
|
628
|
+
user.prefixed_encoded_id
|
|
629
|
+
|
|
630
|
+
# Clear and regenerate encoded IDs
|
|
631
|
+
user.clear_encoded_id_cache!
|
|
632
|
+
user.encoded_id # Regenerates
|
|
418
633
|
```
|
|
419
634
|
|
|
420
635
|
## Security Considerations
|
|
@@ -423,11 +638,14 @@ decoded = User.decode_encoded_id(encoded)
|
|
|
423
638
|
- Don't rely on them for authentication or authorization
|
|
424
639
|
- They help prevent enumeration attacks but aren't cryptographically secure
|
|
425
640
|
- Always validate decoded IDs before database operations
|
|
641
|
+
- Use `find_by_encoded_id!` to ensure proper error handling
|
|
426
642
|
|
|
427
643
|
## Example Use Cases
|
|
428
644
|
|
|
429
645
|
1. **Public-Facing IDs**: Hide sequential database IDs from users
|
|
430
|
-
2. **SEO-Friendly URLs**: Combine slugs with encoded IDs
|
|
646
|
+
2. **SEO-Friendly URLs**: Combine slugs with encoded IDs (`cool-gadget--product_k6j8`)
|
|
431
647
|
3. **API Design**: Provide opaque identifiers that don't leak information
|
|
432
648
|
4. **Multi-Tenant Apps**: Use different salts per tenant for isolation
|
|
433
|
-
5. **Legacy Migration**: Gradually move from numeric to encoded IDs
|
|
649
|
+
5. **Legacy Migration**: Gradually move from numeric to encoded IDs
|
|
650
|
+
6. **Referral Codes**: Encode user IDs into shareable links
|
|
651
|
+
7. **Soft Launches**: Hide actual user/item counts from competitors
|