encoded_id-rails 1.0.0.rc1 → 1.0.0.rc6

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -18
  3. data/LICENSE.txt +1 -1
  4. data/README.md +76 -479
  5. data/context/encoded_id-rails.md +433 -0
  6. data/context/encoded_id.md +283 -0
  7. data/lib/encoded_id/rails/active_record_finders.rb +52 -0
  8. data/lib/encoded_id/rails/annotated_id.rb +8 -0
  9. data/lib/encoded_id/rails/annotated_id_parser.rb +8 -1
  10. data/lib/encoded_id/rails/coder.rb +20 -2
  11. data/lib/encoded_id/rails/configuration.rb +44 -4
  12. data/lib/encoded_id/rails/encoder_methods.rb +9 -1
  13. data/lib/encoded_id/rails/finder_methods.rb +10 -0
  14. data/lib/encoded_id/rails/model.rb +25 -2
  15. data/lib/encoded_id/rails/path_param.rb +7 -0
  16. data/lib/encoded_id/rails/persists.rb +52 -8
  17. data/lib/encoded_id/rails/query_methods.rb +20 -4
  18. data/lib/encoded_id/rails/railtie.rb +13 -0
  19. data/lib/encoded_id/rails/salt.rb +7 -0
  20. data/lib/encoded_id/rails/slugged_id.rb +8 -0
  21. data/lib/encoded_id/rails/slugged_id_parser.rb +8 -1
  22. data/lib/encoded_id/rails/slugged_path_param.rb +7 -0
  23. data/lib/encoded_id/rails.rb +9 -6
  24. data/lib/generators/encoded_id/rails/templates/encoded_id.rb +22 -2
  25. metadata +13 -23
  26. data/.devcontainer/Dockerfile +0 -17
  27. data/.devcontainer/compose.yml +0 -10
  28. data/.devcontainer/devcontainer.json +0 -12
  29. data/.standard.yml +0 -3
  30. data/Appraisals +0 -9
  31. data/Gemfile +0 -24
  32. data/Rakefile +0 -20
  33. data/Steepfile +0 -4
  34. data/gemfiles/.bundle/config +0 -2
  35. data/gemfiles/rails_7.2.gemfile +0 -19
  36. data/gemfiles/rails_8.0.gemfile +0 -19
  37. data/lib/encoded_id/rails/version.rb +0 -7
  38. data/rbs_collection.yaml +0 -24
  39. data/sig/encoded_id/rails.rbs +0 -141
@@ -0,0 +1,433 @@
1
+ # EncodedId::Rails - Rails Integration Technical Documentation
2
+
3
+ ## Overview
4
+
5
+ `encoded_id-rails` provides seamless Rails integration for the `encoded_id` gem, enabling ActiveRecord models to use obfuscated IDs in URLs while maintaining standard Rails conventions. It offers multiple integration strategies from basic encoding to full ActiveRecord finder method overrides.
6
+
7
+ ## Key Features
8
+
9
+ - **ActiveRecord Integration**: Works seamlessly with Rails models
10
+ - **URL-Friendly IDs**: Automatic `to_param` overrides for encoded IDs in routes
11
+ - **Slugged IDs**: Human-readable slugs combined with encoded IDs (e.g., `john-doe--user_p5w9-z27j`)
12
+ - **Annotated IDs**: Model type prefixes for clarity (e.g., `user_p5w9-z27j`)
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
16
+ - **ActiveRecord Finder Overrides**: Seamless integration with `find`, `find_by_id`, etc.
17
+
18
+ ## Installation & Setup
19
+
20
+ ```bash
21
+ # Add to Gemfile
22
+ gem 'encoded_id-rails'
23
+
24
+ # Install
25
+ bundle install
26
+
27
+ # Generate configuration
28
+ rails generate encoded_id:rails:install
29
+ ```
30
+
31
+ This creates `config/initializers/encoded_id.rb` with configuration options.
32
+
33
+ ## Core Modules
34
+
35
+ ### EncodedId::Rails::Model
36
+
37
+ The base module that provides core functionality.
38
+
39
+ ```ruby
40
+ class User < ApplicationRecord
41
+ include EncodedId::Rails::Model
42
+ end
43
+ ```
44
+
45
+ #### Instance Methods
46
+
47
+ ##### encoded_id
48
+ Returns the encoded ID with optional annotation prefix.
49
+
50
+ ```ruby
51
+ user = User.find(123)
52
+ user.encoded_id # => "user_p5w9-z27j"
53
+ ```
54
+
55
+ ##### slugged_encoded_id
56
+ Returns encoded ID with human-readable slug.
57
+
58
+ ```ruby
59
+ class User < ApplicationRecord
60
+ include EncodedId::Rails::Model
61
+
62
+ def name_for_encoded_id_slug
63
+ full_name.parameterize
64
+ end
65
+ end
66
+
67
+ user.slugged_encoded_id # => "john-doe--user_p5w9-z27j"
68
+ ```
69
+
70
+ ##### annotation_for_encoded_id
71
+ Override to customize the annotation prefix.
72
+
73
+ ```ruby
74
+ def annotation_for_encoded_id
75
+ "usr" # => "usr_p5w9-z27j"
76
+ end
77
+ ```
78
+
79
+ #### Class Methods
80
+
81
+ ##### find_by_encoded_id(encoded_id)
82
+ Find record by encoded ID (returns nil if not found).
83
+
84
+ ```ruby
85
+ User.find_by_encoded_id("user_p5w9-z27j") # With annotation
86
+ User.find_by_encoded_id("p5w9-z27j") # Just the hash
87
+ User.find_by_encoded_id("john-doe--user_p5w9-z27j") # Slugged
88
+ ```
89
+
90
+ ##### find_by_encoded_id!(encoded_id)
91
+ Same as above but raises `ActiveRecord::RecordNotFound` if not found.
92
+
93
+ ##### find_all_by_encoded_id(encoded_id)
94
+ Find multiple records when encoded ID contains multiple IDs.
95
+
96
+ ```ruby
97
+ # If encoded ID represents [78, 45]
98
+ User.find_all_by_encoded_id("z2j7-0dmw")
99
+ # => [#<User id: 78>, #<User id: 45>]
100
+ ```
101
+
102
+ ##### where_encoded_id(encoded_id)
103
+ Returns ActiveRecord relation for chaining.
104
+
105
+ ```ruby
106
+ User.where_encoded_id("user_p5w9-z27j").where(active: true)
107
+ ```
108
+
109
+ ##### encode_encoded_id(id)
110
+ Encode a specific ID using model's configuration.
111
+
112
+ ```ruby
113
+ User.encode_encoded_id(123) # => "p5w9-z27j"
114
+ ```
115
+
116
+ ### EncodedId::Rails::PathParam
117
+
118
+ Makes models use encoded IDs in URL helpers.
119
+
120
+ ```ruby
121
+ class User < ApplicationRecord
122
+ include EncodedId::Rails::Model
123
+ include EncodedId::Rails::PathParam
124
+ end
125
+
126
+ user.to_param # => "user_p5w9-z27j"
127
+
128
+ # In routes
129
+ link_to "View", user_path(user) # => "/users/user_p5w9-z27j"
130
+ ```
131
+
132
+ ### EncodedId::Rails::SluggedPathParam
133
+
134
+ Uses slugged encoded IDs in URLs.
135
+
136
+ ```ruby
137
+ class User < ApplicationRecord
138
+ include EncodedId::Rails::Model
139
+ include EncodedId::Rails::SluggedPathParam
140
+
141
+ def name_for_encoded_id_slug
142
+ full_name.parameterize
143
+ end
144
+ end
145
+
146
+ user.to_param # => "john-doe--user_p5w9-z27j"
147
+ ```
148
+
149
+ ### EncodedId::Rails::ActiveRecordFinders
150
+
151
+ Overrides standard ActiveRecord finders to handle encoded IDs transparently.
152
+
153
+ ```ruby
154
+ class Product < ApplicationRecord
155
+ include EncodedId::Rails::Model
156
+ include EncodedId::Rails::ActiveRecordFinders
157
+ end
158
+
159
+ # Now these all work with encoded IDs
160
+ Product.find("product_p5w9-z27j")
161
+ Product.find_by_id("product_p5w9-z27j")
162
+ Product.where(id: "product_p5w9-z27j")
163
+
164
+ # Still works with regular IDs
165
+ Product.find(123)
166
+
167
+ # In controllers, no changes needed
168
+ def show
169
+ @product = Product.find(params[:id]) # Works with both
170
+ end
171
+ ```
172
+
173
+ **Warning**: Do NOT use with string-based primary keys (UUIDs).
174
+
175
+ ### EncodedId::Rails::Persists
176
+
177
+ Stores encoded IDs in database for performance.
178
+
179
+ ```bash
180
+ # Generate migration
181
+ rails generate encoded_id:rails:add_columns User
182
+
183
+ # Adds columns:
184
+ # - normalized_encoded_id (string)
185
+ # - prefixed_encoded_id (string)
186
+ ```
187
+
188
+ ```ruby
189
+ class User < ApplicationRecord
190
+ include EncodedId::Rails::Model
191
+ include EncodedId::Rails::Persists
192
+ end
193
+
194
+ # Fast lookups via direct DB query
195
+ User.where(normalized_encoded_id: "p5w9z27j").first
196
+
197
+ # Add index for performance
198
+ add_index :users, :normalized_encoded_id, unique: true
199
+ ```
200
+
201
+ ## Configuration
202
+
203
+ ### Global Configuration
204
+
205
+ ```ruby
206
+ # config/initializers/encoded_id.rb
207
+ EncodedId::Rails.configure do |config|
208
+ # Required
209
+ config.salt = "your-secret-salt"
210
+
211
+ # Optional
212
+ config.id_length = 8 # Minimum length
213
+ config.character_group_size = 4 # Split every X chars
214
+ config.group_separator = "-" # Split character
215
+ config.alphabet = EncodedId::Alphabet.modified_crockford
216
+ config.annotation_method_name = :annotation_for_encoded_id
217
+ config.annotated_id_separator = "_"
218
+ config.slug_value_method_name = :name_for_encoded_id_slug
219
+ config.slugged_id_separator = "--"
220
+ 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
+ end
225
+ ```
226
+
227
+ ### Per-Model Configuration
228
+
229
+ Override salt per model:
230
+
231
+ ```ruby
232
+ class User < ApplicationRecord
233
+ include EncodedId::Rails::Model
234
+
235
+ def self.encoded_id_salt
236
+ "user-specific-salt"
237
+ end
238
+ end
239
+ ```
240
+
241
+ ### Advanced Model Configuration
242
+
243
+ Full control via `encoded_id_coder`:
244
+
245
+ ```ruby
246
+ class Product < ApplicationRecord
247
+ include EncodedId::Rails::Model
248
+
249
+ def self.encoded_id_coder(options = {})
250
+ super(options.merge(
251
+ encoder: :sqids,
252
+ id_length: 12,
253
+ character_group_size: 3,
254
+ group_separator: ".",
255
+ alphabet: EncodedId::Alphabet.new("0123456789ABCDEF"),
256
+ blocklist: ["BAD", "FAKE"]
257
+ ))
258
+ end
259
+ end
260
+ ```
261
+
262
+ ### Contextual Encoding
263
+
264
+ Different configurations for different use cases:
265
+
266
+ ```ruby
267
+ class User < ApplicationRecord
268
+ include EncodedId::Rails::Model
269
+
270
+ # Short ID for QR codes
271
+ def qr_encoded_id
272
+ self.class.encode_encoded_id(id,
273
+ id_length: 6,
274
+ character_group_size: nil
275
+ )
276
+ end
277
+
278
+ # API-friendly (no separators/annotations)
279
+ def api_encoded_id
280
+ self.class.encode_encoded_id(id,
281
+ character_group_size: nil,
282
+ annotation_method_name: nil
283
+ )
284
+ end
285
+ end
286
+ ```
287
+
288
+ ## Routes & Controllers
289
+
290
+ ### Basic Setup
291
+
292
+ ```ruby
293
+ # routes.rb
294
+ Rails.application.routes.draw do
295
+ resources :users, param: :encoded_id
296
+ end
297
+
298
+ # UsersController
299
+ class UsersController < ApplicationController
300
+ def show
301
+ @user = User.find_by_encoded_id!(params[:encoded_id])
302
+ end
303
+ end
304
+ ```
305
+
306
+ ### With ActiveRecordFinders
307
+
308
+ ```ruby
309
+ # routes.rb - standard :id param
310
+ resources :products
311
+
312
+ # ProductsController
313
+ class ProductsController < ApplicationController
314
+ def show
315
+ # Works with both regular and encoded IDs
316
+ @product = Product.find(params[:id])
317
+ end
318
+ end
319
+ ```
320
+
321
+ ## Common Patterns
322
+
323
+ ### Complete Integration Example
324
+
325
+ ```ruby
326
+ class Product < ApplicationRecord
327
+ include EncodedId::Rails::Model
328
+ include EncodedId::Rails::SluggedPathParam
329
+ include EncodedId::Rails::Persists
330
+ include EncodedId::Rails::ActiveRecordFinders
331
+
332
+ def name_for_encoded_id_slug
333
+ name.parameterize
334
+ 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
+ end
344
+
345
+ # Usage
346
+ product = Product.create(name: "Cool Gadget")
347
+ product.encoded_id # => "product_k6jR8Myo23"
348
+ product.slugged_encoded_id # => "cool-gadget--product_k6jR8Myo23"
349
+
350
+ # All these work
351
+ Product.find("product_k6jR8Myo23")
352
+ Product.find("cool-gadget--product_k6jR8Myo23")
353
+ Product.find_by_encoded_id("k6jR8Myo23")
354
+ Product.where_encoded_id("product_k6jR8Myo23").active
355
+
356
+ # URLs automatically use slugged IDs
357
+ product_path(product) # => "/products/cool-gadget--product_k6jR8Myo23"
358
+ ```
359
+
360
+ ### Migration for Existing Data
361
+
362
+ ```ruby
363
+ # For persisted encoded IDs
364
+ User.find_each(batch_size: 1000) do |user|
365
+ user.set_normalized_encoded_id!
366
+ end
367
+
368
+ # Or via background job
369
+ class BackfillEncodedIdsJob < ApplicationJob
370
+ def perform(model_class, start_id, end_id)
371
+ model_class.where(id: start_id..end_id).find_each do |record|
372
+ record.set_normalized_encoded_id!
373
+ end
374
+ end
375
+ end
376
+ ```
377
+
378
+ ## Performance Considerations
379
+
380
+ 1. **Persistence**: Use `EncodedId::Rails::Persists` for high-traffic lookups
381
+ 2. **Indexes**: Add database indexes on `normalized_encoded_id`
382
+ 3. **Caching**: Encoded IDs are deterministic - cache them if needed
383
+ 4. **Blocklists**: Large blocklists impact performance, especially with Sqids
384
+
385
+ ## Best Practices
386
+
387
+ 1. **Consistent Configuration**: Don't change salt/encoder after going to production
388
+ 2. **Model Naming**: Use clear annotation prefixes to identify model types
389
+ 3. **Error Handling**: Always use `find_by_encoded_id!` in controllers for proper 404s
390
+ 4. **URL Design**: Choose between encoded IDs vs slugged IDs based on UX needs
391
+ 5. **Testing**: Test with both regular IDs and encoded IDs in your specs
392
+
393
+ ## Troubleshooting
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
408
+
409
+ ```ruby
410
+ # Check configuration
411
+ user = User.first
412
+ user.class.encoded_id_salt
413
+ user.class.encoded_id_coder.encoder
414
+
415
+ # Test encoding/decoding
416
+ encoded = User.encode_encoded_id(123)
417
+ decoded = User.decode_encoded_id(encoded)
418
+ ```
419
+
420
+ ## Security Considerations
421
+
422
+ - Encoded IDs are obfuscated, NOT encrypted
423
+ - Don't rely on them for authentication or authorization
424
+ - They help prevent enumeration attacks but aren't cryptographically secure
425
+ - Always validate decoded IDs before database operations
426
+
427
+ ## Example Use Cases
428
+
429
+ 1. **Public-Facing IDs**: Hide sequential database IDs from users
430
+ 2. **SEO-Friendly URLs**: Combine slugs with encoded IDs for best of both worlds
431
+ 3. **API Design**: Provide opaque identifiers that don't leak information
432
+ 4. **Multi-Tenant Apps**: Use different salts per tenant for isolation
433
+ 5. **Legacy Migration**: Gradually move from numeric to encoded IDs
@@ -0,0 +1,283 @@
1
+ # EncodedId Ruby Gem - Technical Documentation
2
+
3
+ ## Overview
4
+
5
+ `encoded_id` is a Ruby gem that provides reversible obfuscation of numerical and hexadecimal IDs into human-readable strings suitable for use in URLs. It offers a secure way to hide sequential database IDs from users while maintaining the ability to decode them back to their original values.
6
+
7
+ ## Key Features
8
+
9
+ - **Reversible Encoding**: Unlike UUIDs, encoded IDs can be decoded back to their original numeric values
10
+ - **Multiple ID Support**: Encode multiple numeric IDs in a single string
11
+ - **Algorithm Choice**: Supports both HashIds and Sqids encoding algorithms
12
+ - **Human-Readable Format**: Character grouping and configurable separators for better readability
13
+ - **Character Mapping**: Handles easily confused characters (0/O, 1/I/l) through equivalence mapping
14
+ - **Performance Optimized**: Uses an optimized HashIds implementation for better performance
15
+ - **Profanity Protection**: Built-in blocklist support to prevent offensive words in generated IDs
16
+ - **Customizable**: Configurable alphabets, lengths, and formatting options
17
+
18
+ ## Core API
19
+
20
+ ### EncodedId::ReversibleId
21
+
22
+ The main class for encoding and decoding IDs.
23
+
24
+ #### Constructor
25
+
26
+ ```ruby
27
+ EncodedId::ReversibleId.new(
28
+ salt:, # Required: String salt (min 4 chars)
29
+ length: 8, # Minimum length of encoded string
30
+ split_at: 4, # Split encoded string every X characters
31
+ split_with: "-", # Character to split with
32
+ alphabet: EncodedId::Alphabet.modified_crockford,
33
+ hex_digit_encoding_group_size: 4,
34
+ max_length: 128, # Maximum length limit
35
+ max_inputs_per_id: 32, # Maximum IDs to encode together
36
+ encoder: :hashids, # :hashids or :sqids
37
+ blocklist: nil # Words to prevent in IDs
38
+ )
39
+ ```
40
+
41
+ #### Key Methods
42
+
43
+ ##### encode(values)
44
+ Encodes one or more integer IDs into an obfuscated string.
45
+
46
+ ```ruby
47
+ coder = EncodedId::ReversibleId.new(salt: "my-salt")
48
+
49
+ # Single ID
50
+ coder.encode(123) # => "p5w9-z27j"
51
+
52
+ # Multiple IDs
53
+ coder.encode([78, 45]) # => "z2j7-0dmw"
54
+ ```
55
+
56
+ ##### decode(encoded_id, downcase: true)
57
+ Decodes an encoded string back to original IDs.
58
+
59
+ ```ruby
60
+ coder.decode("p5w9-z27j") # => [123]
61
+ coder.decode("z2j7-0dmw") # => [78, 45]
62
+
63
+ # Handles confused characters
64
+ coder.decode("p5w9-z27J") # => [123]
65
+ ```
66
+
67
+ ##### encode_hex(hex_strings) (Experimental)
68
+ Encodes hexadecimal strings (like UUIDs).
69
+
70
+ ```ruby
71
+ # Encode UUID
72
+ coder.encode_hex("9a566b8b-8618-42ab-8db7-a5a0276401fd")
73
+ # => "5jjy-c8d9-hxp2-qsve-rgh9-rxnt-7nb5-tve7-bf84-vr"
74
+
75
+ # With larger group size for shorter output
76
+ coder = EncodedId::ReversibleId.new(
77
+ salt: "my-salt",
78
+ hex_digit_encoding_group_size: 32
79
+ )
80
+ coder.encode_hex("9a566b8b-8618-42ab-8db7-a5a0276401fd")
81
+ # => "vr7m-qra8-m5y6-dkgj-5rqr-q44e-gp4a-52"
82
+ ```
83
+
84
+ ##### decode_hex(encoded_id, downcase: true) (Experimental)
85
+ Decodes back to hexadecimal strings.
86
+
87
+ ```ruby
88
+ coder.decode_hex("w72a-y0az") # => ["10f8c"]
89
+ ```
90
+
91
+ ### EncodedId::Alphabet
92
+
93
+ Class for creating custom alphabets.
94
+
95
+ #### Predefined Alphabets
96
+
97
+ ```ruby
98
+ # Default: modified Crockford Base32
99
+ # Characters: "0123456789abcdefghjkmnpqrstuvwxyz"
100
+ # Excludes: i, l, o, u (easily confused)
101
+ # Equivalences: {"o"=>"0", "i"=>"j", "l"=>"1", ...}
102
+ EncodedId::Alphabet.modified_crockford
103
+ ```
104
+
105
+ #### Custom Alphabets
106
+
107
+ ```ruby
108
+ # Simple custom alphabet
109
+ alphabet = EncodedId::Alphabet.new("0123456789abcdef")
110
+
111
+ # With character equivalences
112
+ alphabet = EncodedId::Alphabet.new(
113
+ "0123456789ABCDEF",
114
+ {"a"=>"A", "b"=>"B", "c"=>"C", "d"=>"D", "e"=>"E", "f"=>"F"}
115
+ )
116
+
117
+ # Greek alphabet example
118
+ alphabet = EncodedId::Alphabet.new("αβγδεζηθικλμνξοπρστυφχψω")
119
+ coder = EncodedId::ReversibleId.new(salt: "my-salt", alphabet: alphabet)
120
+ coder.encode(123) # => "θεαψ-ζκυο"
121
+ ```
122
+
123
+ ## Configuration Options
124
+
125
+ ### Basic Options
126
+
127
+ - **salt**: Required secret salt (minimum 4 characters). Changing the salt changes all encoded IDs
128
+ - **length**: Minimum length of encoded string (default: 8)
129
+ - **max_length**: Maximum allowed length (default: 128) to prevent DoS attacks
130
+ - **max_inputs_per_id**: Maximum IDs encodable together (default: 32)
131
+
132
+ ### Encoder Selection
133
+
134
+ ```ruby
135
+ # Default HashIds encoder
136
+ coder = EncodedId::ReversibleId.new(salt: "my-salt")
137
+
138
+ # Sqids encoder (requires 'sqids' gem)
139
+ coder = EncodedId::ReversibleId.new(salt: "my-salt", encoder: :sqids)
140
+ ```
141
+
142
+ **Important**: HashIds and Sqids produce different encodings and are not compatible.
143
+
144
+ ### Formatting Options
145
+
146
+ ```ruby
147
+ # Custom splitting
148
+ coder = EncodedId::ReversibleId.new(
149
+ salt: "my-salt",
150
+ split_at: 3, # Group every 3 chars
151
+ split_with: "." # Use dots
152
+ )
153
+ coder.encode(123) # => "p5w.9z2.7j"
154
+
155
+ # No splitting
156
+ coder = EncodedId::ReversibleId.new(
157
+ salt: "my-salt",
158
+ split_at: nil
159
+ )
160
+ coder.encode(123) # => "p5w9z27j"
161
+ ```
162
+
163
+ ### Blocklist Configuration
164
+
165
+ ```ruby
166
+ # Prevent specific words
167
+ coder = EncodedId::ReversibleId.new(
168
+ salt: "my-salt",
169
+ blocklist: ["bad", "offensive", "words"]
170
+ )
171
+
172
+ # Behavior differs by encoder:
173
+ # - HashIds: Raises error if blocklisted word appears
174
+ # - Sqids: Automatically avoids generating blocklisted words
175
+ ```
176
+
177
+ ## Exception Handling
178
+
179
+ | Exception | Description |
180
+ |-----------|-------------|
181
+ | `EncodedId::InvalidConfigurationError` | Invalid configuration parameters |
182
+ | `EncodedId::InvalidAlphabetError` | Invalid alphabet (< 16 unique chars) |
183
+ | `EncodedId::EncodedIdFormatError` | Invalid encoded ID format |
184
+ | `EncodedId::EncodedIdLengthError` | Encoded ID exceeds max_length |
185
+ | `EncodedId::InvalidInputError` | Invalid input (negative integers, too many inputs) |
186
+ | `EncodedId::SaltError` | Invalid salt (too short) |
187
+
188
+ ## Usage Examples
189
+
190
+ ### Basic Usage
191
+ ```ruby
192
+ # Initialize
193
+ coder = EncodedId::ReversibleId.new(salt: "my-secret-salt")
194
+
195
+ # Encode/decode cycle
196
+ encoded = coder.encode(123) # => "p5w9-z27j"
197
+ decoded = coder.decode(encoded) # => [123]
198
+ original_id = decoded.first # => 123
199
+ ```
200
+
201
+ ### Multiple IDs
202
+ ```ruby
203
+ # Encode multiple IDs in one string
204
+ encoded = coder.encode([78, 45, 92]) # => "z2j7-0dmw-kf8p"
205
+ decoded = coder.decode(encoded) # => [78, 45, 92]
206
+ ```
207
+
208
+ ### Custom Configuration
209
+ ```ruby
210
+ # Highly customized instance
211
+ coder = EncodedId::ReversibleId.new(
212
+ salt: "my-app-salt",
213
+ encoder: :sqids,
214
+ length: 12,
215
+ split_at: 3,
216
+ split_with: ".",
217
+ alphabet: EncodedId::Alphabet.new("0123456789ABCDEF"),
218
+ blocklist: ["BAD", "FAKE"]
219
+ )
220
+ ```
221
+
222
+ ### Hex Encoding (UUIDs)
223
+ ```ruby
224
+ # For encoding UUIDs efficiently
225
+ coder = EncodedId::ReversibleId.new(
226
+ salt: "my-salt",
227
+ hex_digit_encoding_group_size: 32
228
+ )
229
+
230
+ uuid = "550e8400-e29b-41d4-a716-446655440000"
231
+ encoded = coder.encode_hex(uuid)
232
+ decoded = coder.decode_hex(encoded).first # => original UUID
233
+ ```
234
+
235
+ ## Performance Considerations
236
+
237
+ 1. **Algorithm Choice**:
238
+ - HashIds: Faster encoding, especially with blocklists
239
+ - Sqids: Faster decoding
240
+
241
+ 2. **Blocklist Impact**: Large blocklists can slow down encoding, especially with Sqids
242
+
243
+ 3. **Length vs Performance**: Longer minimum lengths may require more computation
244
+
245
+ 4. **Memory Usage**: The gem uses optimized implementations to minimize memory allocation
246
+
247
+ ## Security Notes
248
+
249
+ **Important**: Encoded IDs are NOT cryptographically secure. They provide obfuscation, not encryption. Do not rely on them for security purposes. They can potentially be reversed through brute-force attacks if the salt is compromised.
250
+
251
+ Use encoded IDs for:
252
+ - Hiding sequential database IDs
253
+ - Creating user-friendly URLs
254
+ - Preventing ID enumeration attacks
255
+
256
+ Do NOT use for:
257
+ - Secure tokens
258
+ - Authentication
259
+ - Sensitive data protection
260
+
261
+ ## Installation
262
+
263
+ ```ruby
264
+ # Gemfile
265
+ gem 'encoded_id'
266
+
267
+ # Or install directly
268
+ gem install encoded_id
269
+ ```
270
+
271
+ For Sqids support:
272
+ ```ruby
273
+ gem 'encoded_id'
274
+ gem 'sqids'
275
+ ```
276
+
277
+ ## Best Practices
278
+
279
+ 1. **Salt Management**: Use a strong, unique salt and store it securely (e.g., environment variables)
280
+ 2. **Consistent Configuration**: Once in production, don't change salt or encoder
281
+ 3. **Error Handling**: Always handle potential exceptions when decoding user input
282
+ 4. **Length Limits**: Set appropriate max_length to prevent DoS attacks
283
+ 5. **Validation**: Validate decoded IDs before using them in database queries