Obfuscate Numerical IDs in Rails

Posted on March 22, 2020 - 3 Minute Read

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.

  1. rails g migration add_hashid_to_articles hashid:string
  2. 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 the hashid to the return value of SecureRandom.urlsafe_base64(5).
  • Then we call this method with a before_validation callback.

    • We add prepend: true to ensure this callback is called before friendly_id set's the slug (note that we have not yet installed friendly_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 a hashid. This ensures that the hashid does not change each time a record is updated.

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.

  1. Add gem 'friendly_id', '~> 5.3' to your Gemfile.
  2. Run the following commands.
rails g migration add_slug_to_articles slug:uniq
rails generate friendly_id
rails db:migrate
  1. Next, update your model so it can use friendly_id to set a slug
class Article < ApplicationRecord
    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
  1. Then update your controller to use friendly by replacing Model.find with Model.friendly.find
class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]
  ...
  private
    def set_article
      @article = Article.friendly.find(params[:id])    end
end
  1. 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.

Categorized In: