🫟 Sqinky
🦑 Sqids for your Active Record models.
What is Sqids?
Sqids (pronounced "squids") is an open-source library that lets you generate short unique identifiers from numbers. These IDs are URL-safe, can encode several numbers, and do not contain common profanity words.
This is what they look like:
https://example.com/CN4XstSource: https://sqids.org/
Sqinky brings Sqids-based identifier encoding1 and decoding directly to your Active Record models.
The library adds a thin access layer on top of Sqids to work effortlessly with short, unique, and URL-safe identifiers. It supports encodings composed of multiple attributes and multiple encodings per model. Sqinky does not store encodings in the database. Instead, it decodes them on the fly back to the initial arguments and uses standard Active Record methods to retrieve the records.
Key features
- Dynamic Encodings: Computed from record attributes on the fly. No extra data is stored in the database.
-
Auto-generated Helpers: Provides
find_by_*,find_by_*!,destroy_by_*, anddelete_by_*methods. -
Multiple Attributes: Encode and decode composite identifiers (e.g.,
[account_id, id]). -
Customizable: Pass any Sqids option (
alphabet,min_length, andblocklist) per encoder.
Installation
Run the following command to add Sqinky to your Gemfile:
bundle add sqinkyUsage
To use it include Sqinky::IdentifierEncoding in your Active Record model and
- add the
encodes_identifiermacro for a single attribute (defaults to:id), or - add the
encodes_identifiersmacro for multiple attributes. - See Parameter Overview for all configuration options.
This generates the encoding methods <attribute>_encoding and <attribute>_encoding! (by default; configurable), and the dynamic Active Record methods find_by_*, find_by_*!, delete_by_*, and destroy_by_*. See Generated Method Overview for a detailed overview.
Basic usage
class Comment < ApplicationRecord
include Sqinky::IdentifierEncoding
encodes_identifier :id
end
# Retrieving the encoding
comment = Comment.create!(text: "Sqinky is the linky that breaks when you drinky.")
comment.id # => 1
comment.id_encoding # => "Uk"
# Finding the comment back
Comment.find_by_id_encoding("Uk") # => #<Comment id: 1, ...>
Comment.find_by_id_encoding!("Uk") # Raises ActiveRecord::RecordNotFound if not found
Comment.destroy_by_id_encoding("Uk")
Comment.delete_by_id_encoding("Uk")Warning
For consistent behavior only use attributes with Integer values >= 0.
Note
id_encoding in all these methods is dynamically generated from the id attribute.
- If you encode a different attribute, e.g.
user_idthe method names change touser_id_encoding. - Composite encodings are connected with
_and_in the specified order:id_and_user_id_encoding. - You can choose your own name with the
as:parameter:as: :token # => instance.token.
Most common workflow
- Receive encoding from a persisted record:
user.id_encoding # => "Uk". - Pass the encoding around, either via URL
/users/Uk/showor store it in a session. - Retrieve the record through the encoding
User.find_by_id_encoding("Uk") # => #<User id: 1, ...>
Multiple Attributes & Custom Names
Sqinky can compose multiple attributes into a single encoding, here: :id and :account_id. Normally, this would dynamically generate id_and_account_id_encoding methods, but here we rename them to token with the as: parameter.
class Invitation < ApplicationRecord
include Sqinky::IdentifierEncoding
# Encode both `id` and `account_id` into a single `token`
encodes_identifiers :id, :account_id, as: :token
end
invitation = Invitation.create!(account_id: 42)
invitation.token # => "ySrS"
# Internally calls find_by(id: 1, account_id: 42)
Invitation.find_by_token("ySrS")Note
Mind the pluralized encodes_identifiers when encoding multiple attributes.
Decoding Helper
If you need to access the decoded hash, you can pass your preferred method name to the decodes_as: parameter, e.g. decodes_as: :id_decoding. This will generate a dynamic class method, that takes a single argument: the encoding.
class Order < ApplicationRecord
include Sqinky::IdentifierEncoding
encodes_identifier :id, as: :public_id, decodes_as: :decode_public_id
end
Order.decode_public_id("86Rf07") # => { id: 1 }Custom Sqids Options
Configure Sqids by passing in the Sqids options: alphabet, min_length, and blocklist. Each coder can have its own configuration.
class Comment < ApplicationRecord
include Sqinky::IdentifierEncoding
encodes_identifier :id, alphabet: "abcdef0123456789", min_length: 10
endMultiple Encodings
A model can have multiple encodings, even for the same attribute, as long as they have distinct as: method names.
class Post < ApplicationRecord
include Sqinky::IdentifierEncoding
encodes_identifier
encodes_identifiers :id, :tenant_id
end
post = Post.create!(title: "How Sqinky became so inkie.")
post.id # => 212
post.tenant_id # => 42
post.id_encoding # => "37E"
post.id_and_tenant_id_encoding # "jGTwn"
Post.find_by_id_encoding("37E") # => #<Post id: 212, ...>
Post.find_by_id_and_tenant_id_encoding("jGTwn") # => #<Post id: 212, ...>Inheritance
Since Sqinky generates regular methods, it can be included anywhere in the class tree. Child classes can use the parents' methods or overwrite them with their own. It isn't required for the including class to have the configured attributes.
class ApplicationRecord < ActiveRecord::Base
include Sqinky::IdentifierEncoding
encodes_identifier
end
class Label < ApplicationRecord
end
label = Label.create!(text: "Pinky")
label.id = 1
label.id_encoding # => "Uk"Parameter Overview
encodes_identifier
| Parameter | Default | Description |
|---|---|---|
attribute |
:id |
The attribute to encode. Should only have Integer >= 0 values. |
as: |
nil |
If nil inferred as <attribute>_encoding. |
decodes_as: |
nil |
Name of the decoding class method. If nil no such method is generated. |
**sqids_options |
{} |
Sqids options passed through to Sqids.new, e.g. min_length, alphabet, and blocklist
|
encodes_identifiers
| Parameter | Default | Description |
|---|---|---|
*attributes |
The attribute(s) to encode. Should only have Integer >= 0 values. |
|
as: |
nil |
If nil inferred as <attribute[_and_<attribute>]>_encoding. |
decodes_as: |
nil |
Name of the decoding class method. If nil no such method is generated. |
**sqids_options |
{} |
Sqids options passed through to Sqids.new, e.g. min_length, alphabet, and blocklist. |
Generated Methods Overview
Sqinky generates these methods when invoking encodes_identifier(s):
| Method | Description |
|---|---|
instance.<as> |
Returns the Sqids encoding for the configured attributes. Returns nil if any attribute is nil. |
instance.<as>! |
Same as above, but raises ArgumentError if any attribute is not an Integer. |
Class.<decodes_as>(encoding) |
Returns the decoded hash, e.g. { id: 42 }. |
Class.find_by_<as>(encoding) |
Decodes encoding and passes the decoded hash to find_by(...). |
Class.find_by_<as>!(encoding) |
Decodes encoding and passes the decoded hash to find_by!(...). |
Class.destroy_by_<as>(encoding) |
Decodes encoding and passes the decoded hash to destroy_by(...). |
Class.delete_by_<as>(encoding) |
Decodes encoding and passes the decoded hash to delete_by(...). |
Development
Setup
After checking out the repo, run the setup script to install dependencies:
bin/setupThis project uses mise for managing Ruby versions and tasks. If you have mise installed, you can use it to run common tasks.
Scripts & Tasks
-
bin/console: Open an interactive prompt to experiment with the code. -
rake spec: Run the test suite. -
rake standard: Run the StandardRB linter. -
bundle exec appraisal install: -
bundle exec appraisal rake spec: Run tests against all supported Rails versions. -
mise run ci: Run the local CI pipeline (linting and multi-Rails tests).
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/david-uhlig/sqinky. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Footnotes
-
In the context of this library the term
encodingrefers to the output ofSqids.new.encode([<number>]). In Sqids terminology this is a short unique identifier from numbers. ↩