Obfuscate Numerical IDs in Rails
By default, Rails displays the record’s ID
in the URL (e.g. http://localhost:3000/articles/1
). Although there is nothing inherently wrong with this approach, sometimes it’s helpful to obfuscate the record’s ID
(e.g. http://localhost:3000/articles/xb3mm6k
). In this tutorial I will show you how to obfuscate numerical IDs
in Rails.
Step 1. Add a Hashid Column to Your Model
In order to obfuscate our record’s IDs
we’ll first need to add a column to our model to store a random value. We can call this column anything, but let’s call it hashid
.
rails g migration add_hashid_to_articles hashid:string
rails db:migrate
Step 2. Set the Value of the Hashid in a Callback
Next we need to programmatically set the value of the hashid
column. There are many ways to achieve this, but I like using SecureRandom.urlsafe_base64 in combination with a before_validation callback.
SecureRandom.urlsafe_base64 generates a random URL-safe base64 string.
1.
class Article < ApplicationRecord
before_validation :set_hashid,
prepend: true,
if: Proc.new { |article| article.hashid.nil? }
private
def set_hashid
self.hashid = SecureRandom.urlsafe_base64(5)
end
end
- First we create a private
set_hashid
method that will set thehashid
to the return value ofSecureRandom.urlsafe_base64(5)
. - Then we call this method with a
before_validation
callback.- We add
prepend: true
to ensure this callback is called beforefriendly_id
set’s theslug
(note that we have not yet installedfriendly_id
). - We use a :if with a Proc to ensure that the
set_hashid
method is only called if the record does not yet have ahashid
. This ensures that thehashid
does not change each time a record is updated.
- We add
Step 3. Install and Configure friendly_id
Now that we are programmatically assigning a hashid
to our model, we need to use that value in the URL. Luckily the friendly_id makes this easy.
- Add
gem 'friendly_id', '~> 5.3'
to yourGemfile
. -
Run the following commands.
rails g migration add_slug_to_articles slug:uniq rails generate friendly_id rails db:migrate
-
Next, update your model so it can use
friendly_id
to set aslug
class Article < ApplicationRecord # ℹ️ Include FriendlyId macro extend FriendlyId friendly_id :hashid, use: :slugged before_validation :set_hashid, prepend: true, if: Proc.new { |article| article.hashid.nil? } private def set_hashid self.hashid = SecureRandom.urlsafe_base64(5) end end
-
Then update your controller to use
friendly
by replacingModel.find
withModel.friendly.find
class ArticlesController < ApplicationController before_action :set_article, only: %i[show edit update destroy] private def set_article # ℹ️ Find the article by the slug @article = Article.friendly.find(params[:id]) end end
- Finally, update any existing records by opening the rails console and running
Article.find_each(&:save)
Conclusion and Next Steps
One important thing to note is that SecureRandom.urlsafe_base64
does not guarantee a unique value. This means that there’s a chance multiple records could have the same value for the hashid
. Fortunately fiendly_id
accounts for any conflicting slugs by appending a UUID to the slug
. If you want more control over the what is appended to the url, you can use candidates.