Advanced Topics
Performance Considerations
In general, at the moment, Sqids are slower to encode than Hashids (especially if using the blocklist feature). However, they are faster to decode than Hashids. However, with YJIT enabled the differences in speeds are smaller.
To get the most out of Sqids encode performance consider a small (or no) blocklist (set the blocklist:
option). The default Sqids blocklist is very costly on encode time, but extensive.
For Rails applications with frequent encoded ID lookups:
-
Use the Persists module: When you frequently look up records by encoded ID, including
EncodedId::Rails::Persists
and adding the necessary database columns can significantly improve performance by avoiding the need to decode IDs. -
Be mindful of slugs: Generating slugs can be expensive if the slug method performs complex operations. Keep your
name_for_encoded_id_slug
implementation efficient. -
Cache encoded IDs: For records that rarely change, consider caching the encoded ID in a cache store like Redis or Memcached.
Security Considerations
It’s important to understand the security implications of using encoded IDs.
Not for Sensitive Data
Encoded IDs are not secure. They are meant to be used for obfuscation, not encryption. It may be possible to reverse them via brute-force, especially for simple or sequential IDs.
Don’t use encoded IDs as the sole protection for sensitive resources. Always implement proper authorization checks.
Salt Management
The salt is critical to the encoding process. Keep these principles in mind:
-
Use a strong salt: Choose a salt that is at least 16 characters and includes mixed case, numbers, and special characters.
-
Keep your salt secret: Store it securely, such as in environment variables or credentials.
-
Changing the salt: If you change your salt, all previously encoded IDs will no longer decode correctly. Have a migration plan if you need to change the salt.
Authorization vs. Obfuscation
Remember that encoded IDs provide obfuscation, not authorization:
# WRONG - relying on encoded ID for security
def show
@resource = Resource.find_by_encoded_id(params[:encoded_id])
# No authorization check!
end
# RIGHT - with proper authorization
def show
@resource = Resource.find_by_encoded_id(params[:encoded_id])
authorize! :read, @resource # Use your authorization library
end
Hex Encoding Features (Experimental)
EncodedId includes experimental support for encoding hex strings, which can be useful for UUIDs and other hex-based identifiers.
Encoding UUIDs
coder = EncodedId::ReversibleId.new(salt: "my-salt")
# Encode a UUID
uuid = "9a566b8b-8618-42ab-8db7-a5a0276401fd"
encoded = coder.encode_hex(uuid)
# => "5jjy-c8d9-hxp2-qsve-rgh9-rxnt-7nb5-tve7-bf84-vr"
# Decode back to UUID
decoded = coder.decode_hex(encoded)
# => ["9a566b8b-8618-42ab-8db7-a5a0276401fd"]
Optimizing Hex Encoding Length
For long hex strings like UUIDs, you can customize the hex_digit_encoding_group_size
to get shorter encoded strings:
# Default hex_digit_encoding_group_size (4)
coder = EncodedId::ReversibleId.new(salt: "my-salt")
encoded = coder.encode_hex("9a566b8b-8618-42ab-8db7-a5a0276401fd")
# => "5jjy-c8d9-hxp2-qsve-rgh9-rxnt-7nb5-tve7-bf84-vr"
# Larger group size for shorter output
coder = EncodedId::ReversibleId.new(
salt: "my-salt",
hex_digit_encoding_group_size: 32
)
encoded = coder.encode_hex("9a566b8b-8618-42ab-8db7-a5a0276401fd")
# => "vr7m-qra8-m5y6-dkgj-5rqr-q44e-gp4a-52"
Complex Use Cases
Multiple Models with Same IDs
When you have multiple models with potentially overlapping IDs, using annotations helps distinguish them:
# User with ID 123
user = User.find(123)
user.encoded_id # => "user_p5w9-z27j"
# Product with ID 123
product = Product.find(123)
product.encoded_id # => "product_p5w9-z27j"
This prevents collisions when decoding.
Sharing Encoded IDs Across Applications
If you need to share encoded IDs across multiple applications:
- Use the same salt: Ensure all applications use the same salt.
- Use the same encoding configuration: Match alphabet, length, etc.
- Share the core library: Use
encoded_id
gem in both applications.
# Application A
coder = EncodedId::ReversibleId.new(salt: "shared-salt")
encoded = coder.encode(123)
# => "p5w9-z27j"
# Application B
coder = EncodedId::ReversibleId.new(salt: "shared-salt")
decoded = coder.decode("p5w9-z27j")
# => [123]
Migration Strategies
When migrating to EncodedId or changing configuration:
- Dual reading: Support both old and new formats temporarily.
- Gradual transition: Introduce encoded IDs in new features first.
- URL redirects: Set up redirects from old ID format to new.
def find_by_any_id(id_param)
# Try to treat as encoded ID first
record = find_by_encoded_id(id_param)
# Fall back to regular ID if encoded ID not found
record ||= begin
direct_id = id_param.to_i
direct_id > 0 ? find_by(id: direct_id) : nil
end
record
end
Troubleshooting
Common Issues
Invalid Encoded ID Format
EncodedId::EncodedIdFormatError: Invalid input
This usually happens when:
- The encoded ID is malformed
- The salt doesn’t match what was used to encode
- The alphabet configuration doesn’t match
Encoded ID Length Exceeded
EncodedId::EncodedIdLengthError: (no message)
This happens when the encoded ID exceeds the configured maximum length. Solutions:
- Increase the
max_length
parameter - Reduce the number of IDs being encoded together
- For hex strings, increase the
hex_digit_encoding_group_size
Debugging Tips
-
Verify salt consistency: Make sure you’re using the same salt for encoding and decoding.
-
Check configuration: Ensure all configuration parameters match between encoding and decoding.
-
Isolate the issue: Try encoding and decoding simple IDs to verify basic functionality.
-
Logging: Add logging around encoding/decoding to see what’s happening.
begin
decoded = coder.decode(encoded_id)
Rails.logger.info("Decoded #{encoded_id} to #{decoded.inspect}")
rescue => e
Rails.logger.error("Failed to decode #{encoded_id}: #{e.message}")
raise
end
- Verify ID type: Make sure you’re not accidentally mixing hex encoding with regular encoding.