Sometimes you don’t want to reveal the real id of the object in the system to the user for some reason. For example, you don’t want to reveal the number of items in the system, or let the user view all of them by simply changing the id in the address.

The most obvious and easy way to do it is to generate a fake id for the object and use it instead. Lets say we have a model named Foo and you want it to have a fake id. Here’s the code for it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Foo < ActiveRecord::Base
  before_validation_on_create :generate_fake_id

  def to_param
    fake_id
  end

protected
  def generate_fake_id
    string = random_string
    while Foo.find_by_fake_id(string)
      string = random_string
    end
    self.fake_id = string
  end
  
  def random_string(size = 8)
    chars = (('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a)
    (1..size).collect{|a| chars[rand(chars.size)] }.join
  end
end
Now, when you create a Foo with for example “Foo.create(:name => ‘first foo’)”, the model generates a random string of 8 characters and stores it in fake_id. The fake id is only generated once, on creation, so it’s not changed later on. Now, for the helpers. Suppose you have map.resources :foos in your routes.rb. The helper you user before, foo_path(@foo) now generates a url with the fake id (foos/Hd45jdg3), because of the to_param method. All you have to do, is to change your foos_controller this way:
1
2
3
4
5
class FoosController < ApplicationController
  def show
    @foo = Foo.find_by_fake_id(params[:id])
  end
end
The uniqueness of the fake_id is guarantied by the Foo.find_by_fake_id call in the generate_fake_id method. Yes, it does add an extra query to the database, but it’s not really impaction performance much unless you are generating a lot of objects all of the time. You can change the way the fake id looks like by modifying the array used in random_string. For example if you want to eliminate symbols that look much alike you can have it this way:
1
2
3
4
def random_string(size = 8)
   chars = (('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a) - %w(i o 0 1 l O)
   (1..size).collect{|a| chars[rand(chars.size)] }.join
end
And also: don’t forget to add the fake_id field to the table in the database.

Sorry, comments are closed for this article.