That is is basically a "fork" of blog article i'm constantly returning to. It seems that the blog is down:
Dave Bass proposed this which I picked up for my implementation (here for an 8-chars token):
>> rand(36**8).to_s(36)
=> "uur0cj2h"
The result can be used as an url; pretty neat. It relies on the ability of Fixnum to translate itself to a string in a given base (here we use base 36, which I rarely use!). This can be used in an ActiveRecord model for instance:
class Customer < ActiveRecord::Base
validates_presence_of :access_token
validates_uniqueness_of :access_token
protected
def before_validation_on_create
self.access_token = rand(36**8).to_s(36) if self.new_record? and self.access_token.nil?
end
end
Jamie proposed several options. First, use a substring of SHA1, which is “small enough to be usable, but still pseudo-random enough for temporary tokens to not be guessable” :
>> require 'digest'
=> []
>> Digest::SHA1.hexdigest("some-random-string")[8..16]
=> "2ebe5597f"
Another technique is to rely on ActiveSupport SecureRandom, and tweak the results a bit to get a url-friendly token. Here’s my final bit of code with this method:
>> require 'active_support'
=> []
>> ActiveSupport::SecureRandom.base64(8).gsub("/","_").gsub(/=+$/,"")
=> "AEWQyovNFo0"
Jamie’s last proposal is “not terribly robust, but functional” :
>> chars = ['A'..'Z', 'a'..'z', '0'..'9'].map{|r|r.to_a}.flatten
>> Array.new(6).map{chars[rand(chars.size)]}.join
=> "g64wdR"
Ryan proposed something totally different:
>> words = File.read("/usr/share/dict/words").split; max = words.size
=> 234936
>> "#{words[rand(max)]}-#{words[rand(max)]}"
=> "loquat-motorial"
The idea is interesting. You’ll need to ensure your dictionary doesn’t contain insults, if your user base cares about that :)
Another option Ryan got from Eric is to use the quite unknown bubble-babble to make hash values more readable:
>> require 'digest/bubblebabble'
=> true
Digest.bubblebabble(Digest::SHA1::hexdigest("random string")[8..12])
=> "xesik-fymak-gunax"
rufus-mnemo has the ability to translate an integer into easy-to-remember words, based on Japanese syllabes:
>> require 'rufus/mnemo'
>> s = Rufus::Mnemo::from_integer rand(8**5)
=> "bisoshi"
Pretty neat! The generated words are “easy to the latin ears”. Take care of the meaning if your users are Japanese-speaking.
If you deploy to Solaris zones, be careful about that: some other libraries I had a look at, like the very nice assaf’s uuid, are relying on macaddr, which doesn’t seem to work on Solaris Zone.
Please share them in comments.