For Inspectinator, I needed a lightweight token generator to allow people to share their results. I found a great solution, and learned a cool ruby trick, too.
The trick is in Fixnum (and Bignum), #to_s takes an optional argument: a number in (2..36). That number is the base it will print the number in (default is 10).
So, for instance (from the ruby docs), to convert a number to binary:
For my token, I went with an 8-character, base 36 number, which gives me almost 3 trillion possible values (should be plenty). To get the 8 characters, I use rand(36**8). I don’t do any checking on the length, so 8 is actually the max length (and 1 is the min). In practice, though, the vast majority of tokens will be the target length. On my system, I generated 10,000,000 tokens, and 9,722,373 were 8 characters. I chose to accept that as “close enough” because 1) it doesn’t really matter to me if tokens are shorter, and 2) I think the simplicity and elegance of this solution is worth a little uncertainty.
Then, (since I’m using active record), it’s just a matter of adding a before_create to set it up:
t = nil
begin
t = rand(36**8).to_s(36)
end while InspectString.find_by_token(t)
self.token = t
end
1) I don't think you're going to have as many possible combinations as you think – Random functions are notorious that way. I wouldn't be surprised if Ruby's rand repeats after a few million values, but it is probably still ok for your purposes.
2) If you need a token, and you are already in Rails, it might be better to use one of the methods in ActiveSupport::SecureRandom – something like this:
token = ActiveSupport::secureRandom.hex(8)
would give you what you are looking for.
bokmann — Thanks for the comments.
1) Good point. Should be able to get by with only a few million values, if that's all I get
2) SecureRandom is a good tip. I'm not in rails and don't want to do ActiveSupport as a dependency but would use that if in rails.
Nice and simple. If you ever do actually care about longer token strings and uniqueness, try this one:
Digest::SHA1.hexdigest(rand(RAND_SEED).to_s)
where RAND_SEED is set at application start up:
RAND_SEED = 4.times.inject(Time.now.to_i){|t, n| t += rand(n)}