I frequently need to generate a short, random string:

  • I need a non-numeric, non-enumerable url ID. e.g. https://example.com/resources/TOKEN
  • I need to generate a short, human-readable random activation token and I don’t want it to contain similar-looking characters like 1I or O0
  • I want to do something fun with strings of emojis.

I’ve been copy-pasting this snippet around for years:

def random_string(alphabet:, length:)
  Array.new(length) { alphabet.chars[rand(alphabet.chars.size)] }.join

random_string(alphabet: "AaBbCc123", length: 7) 
# => "C113A11", "Abcc3B2", "33cabbC", etc.

random_string(alphabet: "πŸ˜€πŸ˜πŸ™ƒπŸ€ͺ😎", length: 3)
# => "πŸ˜πŸ˜€πŸ˜Ž", "πŸ˜€πŸ€ͺπŸ€ͺ", "πŸ™ƒπŸ˜ŽπŸ˜€", etc.

The gems friendly_id and uniquify can do this too.

There was recently a discussion on Ruby on Rails Link Slack about generating random strings, and I went looking to see if there was a better implementation.

I found SecureRandom.choose, which is built into the Ruby standard library, and as part of the SecureRandom module, should be fairly trustworthy. Looks perfect:

# SecureRandom.choose generates a string that randomly draws from a source array of characters.
# The argument _source_ specifies the array of characters from which to generate the string.
# The argument _n_ specifies the length, in characters, of the string to be generated.
# The result may contain whatever characters are in the source array.
#   require 'securerandom'
#   SecureRandom.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron"
#   SecureRandom.choose([*'0'..'9'], 5)  #=> "27309"

…but there is a problem: SecureRandom.choose is a private method. The choose method is used to implement the public SecureRandom.alphanumeric method, but is not itself exposed publicly. I went back to the initial feature request and found the reason:

I feel the method name, SecureRandom.choose, doesn’t represent the behavior well.

Fair enough. Until the name is figured out, I’ll still be using it:

require 'securerandom'

SecureRandom.send :choose, "πŸ˜€πŸ˜πŸ™ƒπŸ€ͺ😎".chars, 3
# => "😍😎😍"