Masked Identifier

Masked Identifier makes it easy to add a non-incrementing identifier to any ActiveRecord object.

Instead of

http://localhost:3000/users/1
user = User.find(1)

with Masked Identifier you can

http://localhost:3000/users/2xfYgm1pQ8
user = User.find_by_masked_identifier('2xfYgm1pQ8')

Installation

Add gem 'masked-identifier' to your Rails project Gemfile.

Verified to work with Rails 3.1.0.rc5. Let me know if you use it in older versions of Rails.

Usage

Add a column named 'masked_identifier' to the table of the ActiveRecord object that you want to be able to find using a masked identifier. Also, don't forget to add an index for this new column

def change
  add_column :users, :masked_identifier
  add_index :users, :masked_identifier
end

Add has_masked_identifier to the ActiveRecord object

class User < ActiveRecord::Base
  has_masked_identifier
end

Create a new object and you'll see that a masked identifier has been added

user = User.create!(name: 'Matt')
user.masked_identifier
=> "2xfYgm1pQ8"

Lookup users based on the object's masked_identifier property

user = User.find_by_masked_identifier('2xfYgm1pQ8')
user.name
=> "Matt"

If you want User to automatically return masked_identifier instead of id when to_param is called, you can override to_param in the User class

class User < ActiveRecord::Base
  def to_param
    self.masked_identifier
  end
end

Options

Masked Identifier lets you edit a number of settings by including an options hash following


```ruby
class User < ActiveRecord::Base
  has_masked_identifier length: 8, property: 'mid', charset: %w{ a b c d }, attempts: 10
end

Note: This is for example purposes only. You should never use a charset this small. You run the risk of running out of possible unique values and throwing a CodeGenerator::TooManyFailedAttempts error.

  • length - the number of characters to include in the masked identifier. Note: As the length is increased, the number of possible unique masked identifiers also increases

  • property - override the default masked_identifier property with a custom named property. Don't forget you'll need to use this custom name in your database migration too

  • charset - override the default character set used when generating masked identifiers (default character set includes a-z, A-Z, and 0-9). Note: As the size of the character set is increased, the number of possible unique masked identifiers also increases

  • attempts - override the number of attempts to identifiy a unique masked identifier. Each 'attempt' requires one SELECT query on the database. If you have a sufficiently large charset and length >=10 chars, your app should rarely require more than one attempt

Exceptions

Make sure you handle the following possible exceptions in your application:

  • [YourActiveRecordObject]::InvalidProperty - if object does not have a masked_identifier property (or the custom property passed to the property option does not exist)

  • CodeGenerator::TooManyFailedAttempts - if unable to find a unique code to use as a masked identifier

  • CodeGenerator::InvalidAttemptsValue - if the value provided to the attempts option is not an Integer or < 1

  • CodeGenerator::InvalidCodeLength - if the value provided to the length option is not an Integer or is < 1

  • CodeGenerator::InvalidCharset - if value provided to the charset is not an Array with a size >= 1