BasedUUID: URL-friendly, Base32-encoded UUIDs for Rails Models

I made a simple Ruby gem to encode UUIDs to a more manageable format

Recently, I wrote about UUIDv7 and how to use it in Rails. The time-sorted UUIDv7 addresses some performance issues associated with UUIDv4-based primary keys, but the experience of using UUIDs is still far from perfect:

https://example.com/posts/226d037c-3b35-40f3-a30b-0ebb78779d9b

The default string representation of a UUID is unnecessarily long (36 characters) and awkward to copy with a double click. Another annoyance is that if you’re looking at a UUID in isolation, it’s hard to tell which model it represents1.

To solve those issues for my Rails apps, I wrote a small gem: BasedUUID. It turns UUIDs into a shorter, URL-friendly format: a 26-character lowercase string encoded with Base32. It also supports optional prefixes, which help you identify the model a token belongs to (inspired by StripeIDs):

user_763j02ryxh8dbs56mgcjqrmmgt #=> e61c802c-7bb1-4357-929a-9064af8a521a
bpo_12dm1qresn83st62reqdw7f7cv  #=> 226d037c-3b35-40f3-a30b-0ebb78779d9b

I chose Base32 because it offers good compromise between compactness, readability and URL-safety. I used the Crockford’s variant, which removes certain characters from the alphabet that can be easily confused with others (I L O U). I don’t really care for that, because it’s unlikely anyone is going to type up those identifiers by hand, but a nice side-effect is that it reduces the chance of accidental obscenity (it doesn’t fully eliminate it, though).

Using BasedUUID with Rails

BasedUUID assumes that you have a UUID primary key (id) in your ActiveRecord model. It doesn’t affect how your primary key UUIDs are stored in the database: prefixes and Base32-encoded strings are only used for presentation.

class BlogPost < ApplicationRecord
  has_based_uuid prefix: :bpo
end

post = BlogPost.last
post.based_uuid #=> bpo_12dm1qresn83st62reqdw7f7cv
post.based_uuid(prefix: false) #=> 12dm1qresn83st62reqdw7f7cv

BlogPost.find_by_based_uuid("bpo_12dm1qresn83st62reqdw7f7cv")

Prefixes allow us to implement generic lookup, where you can pass any ID and BasedUUID will figure out which model to look for:

BasedUUID.find("bpo_12dm1qresn83st62reqdw7f7cv")

BasedUUID can be used outside ActiveRecord, too. You can use it to encode any UUID:

BasedUUID.encode(uuid: "226d037c-3b35-40f3-a30b-0ebb78779d9b", prefix: :bpo)
BasedUUID.decode("bpo_12dm1qresn83st62reqdw7f7cv")

The result is only ~150 lines of code.

You can find more details and usage examples on the project page on GitHub: https://github.com/pch/based_uuid

If you decide to try it, let me know what you think.

Further reading


  1. Of course, sequential IDs suffer from the same issue, so it’s not a UUID-specific drawback. ↩︎

Published on (Updated: )