How to generate a random string in Ruby
Asked Answered
E

46

830

I'm currently generating an 8-character pseudo-random uppercase string for "A" .. "Z":

value = ""; 8.times{value  << (65 + rand(25)).chr}

but it doesn't look clean, and it can't be passed as an argument since it isn't a single statement. To get a mixed-case string "a" .. "z" plus "A" .. "Z", I changed it to:

value = ""; 8.times{value << ((rand(2)==1?65:97) + rand(25)).chr}

but it looks like trash.

Does anyone have a better method?

Ecclesiastes answered 17/9, 2008 at 22:24 Comment(5)
I don't understand why you care that "since it isn't a single statement it can't be passed as an argument". Why not just make it a utility or helper method?Porphyroid
Suppose there is a method to reset a user's password and it has an argument for the new password. I would like to pass in a random string, in the above code I need a tmp variable, whereas in the single statement examples bellow I can do the whole thing as a one liner. Sure a utility method could be nice in the long run, esp if I'm needing similar here and there, but sometimes you just want it in place, one time, done.Ecclesiastes
No, you don't have to use a temporary variable. Try this: reset_user_password!(random_string) where def random_string; SecureRandom.urlsafe_base64(20) endPorphyroid
8 letters is a shamefully weak password. Given the md5sum a modern PC could recover the password in 30 seconds. How about something longer securerandom.urlsafe_base64Eglanteen
well, put a webdesigner infront of VIM and ask him to save and exit; Jokes apart. require 'securerandom'; SecureRandom.hex(15) should work fineArdennes
H
1042
(0...8).map { (65 + rand(26)).chr }.join

I spend too much time golfing.

(0...50).map { ('a'..'z').to_a[rand(26)] }.join

And a last one that's even more confusing, but more flexible and wastes fewer cycles:

o = [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
string = (0...50).map { o[rand(o.length)] }.join

If you want to generate some random text then use the following:

50.times.map { (0...(rand(10))).map { ('a'..'z').to_a[rand(26)] }.join }.join(" ")

this code generates 50 random word string with words length less than 10 characters and then join with space

Housing answered 17/9, 2008 at 22:24 Comment(13)
34 characters and blazing fast: ('a'..'z').to_a.shuffle[0,8].join. Note you'll need Ruby >=1.9 to shuffle.Tibbitts
first example should be rand(26) otherwise you never get the letter 'Z'Sympathin
Leveraging existing libraries is preferable unless you have a driver to roll your own. See SecureRandom as one example, in the other answers.Porphyroid
@faraz your method isn't functionally the same, it's not random with replacement.Easterling
Touche @michaeltwofish. Here's the shortest and most performant one-liner I could muster: [].fill(0,l){rand(65..90).chr}.join (35 chars). fill is faster than map, but for real performance, drop the golf and prebuild the array with a proper use of sample: chars=["A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];[].fill(0,l){chars.sample}.join That not even significantly faster for 8 characters. With 8 characters, no worries. Anything flies. ;) See my full benchmarks for details.Tibbitts
[*('a'..'z'),*('0'..'9')].shuffle[0,8].join to generate a random string with both letters and numbers.Setter
rand is deterministic and predictable. Don't use this for generating passwords! Use one of the SecureRandom solutions instead.Sopping
the last example should read (0...50).map { [('a'..'z'),('A'..'Z')].flat_map(&:to_a).sample }.join you definitely have not spent too much time golfing xDToneme
further reduced from Dennis's answer (0..50).map { [*'a'..'z',*'A'..'Z'].sample }.joinConfirmatory
Always use existing solutions like SecureRandom in favor of homegrown "random" solutions. Seriously, they are there for a reason.Culvert
@TanelSuurhans As the OP of this answer I agree with all the sentiments regarding "use proven tested tools". Just at the time of this answer, such tools were not core yet, and author appeared to have golfing as an objective. Understanding how your language works is just as important as using tools written in the language.Housing
The solutions with shuffle wouldn't be that awesome, as they'd generate strings with one occurrence of a character ( so string such as "gbalgqut" would be impossible - double "g" )Capuchin
If golf is the objective, ('a'..'z') is identical to (?a..?z); two characters saved.Moderation
M
867

Why not use SecureRandom?

require 'securerandom'
random_string = SecureRandom.hex

# outputs: 5b5cd0da3121fc53b4bc84d0c8af2e81 (i.e. 32 chars of 0..9, a..f)

SecureRandom also has methods for:

  • base64
  • random_bytes
  • random_number

see: http://ruby-doc.org/stdlib-1.9.2/libdoc/securerandom/rdoc/SecureRandom.html

Modicum answered 17/9, 2008 at 22:24 Comment(14)
This generates strings that aren't safe for passing as get variables for websites - for example, you can get string=+fGH1 (+ is read as a space), and string=fyhi/ (the slash is used for navigation)Archive
base64 would, but not hex like in his exampleAnandrous
By the way, it's part of the stdlib in 1.9 and recent 1.8 versions, so one can just require 'securerandom' to get this neat SecureRandom helper :)Adios
@JeffDickey no, thats wrong. SecureRandom.base64 => "7p0lfNClJwolM9BJcgG5lQ==" as you see, there are = in it. Method hex only returns 0-9a-fBobbinet
BTW SecureRandom was removed from ActiveSupport in version 3.2. From the changelog: "Removed ActiveSupport::SecureRandom in favor of SecureRandom from the standard library".Prevail
SecureRandom.random_number(36**12).to_s(36).rjust(12, "0") will generate a string with 0-9a-z (36 characters) that is ALWAYS 12 characters long. Change 12 to whatever length you want. Unfortunately no way to just get A-Z using Integer#to_s.Platonism
@Archive that is wrong. If you wanted to pass +fGH1 through a URL, you just need to URL-encode it like you would ANY value that's going through a URL: %2BfGH1Largess
To generate a string containing only 5 alpha characters you could do this SecureRandom.hex.gsub(/\d/, "")[0..4]Austenite
Hi, I like SecureRandom, but how do I get a string of 6 numbers, each digit with range 0-9. And I would like it in a way that it's easy to stub in my Rails controller spec.Dewberry
I tried 'SecureRandom.random_number(9**6)' but it sometimes returns 5 numbers but it sometimes returns 6 numbers.Dewberry
What are the chances of getting a duplicate?Blackness
@Blackness hello from the future! 👋🏻 The chances are low enough that you generally don't need to worry about it with something like SecureRandom.hex, but it's not impossible. If you're using this to generate unique IDs, use SecureRandom.uuid.Heliochrome
@Heliochrome Err, SecureRandom.uuid is exactly as likely to generate duplicates as SecureRandom.hex. They are two different encodings for 128 random bits.Sevik
Right you are, thanks for the callout! I was thinking of Type 1 UUIDs, but this method generates a Type 4.Heliochrome
V
272

I use this for generating random URL friendly strings with a length between 1 and string_length characters:

string_length = 8
rand(36**string_length).to_s(36)

It generates random strings of lowercase a-z and 0-9. It's not very customizable but it's short and clean.

Vernita answered 17/9, 2008 at 22:24 Comment(12)
+1 for the shortest version (that doesn't call external binaries ^^). If the random string isn't public facing, I sometimes even just use rand.to_s; ugly, but works.Untenable
This is a great solution (and fast, too), but it will occasionally produce a string under length length, roughly once in ~40Yellowbird
@Brian E this would guarantee the digits you want: (36**(length-1) + rand(36**length)).to_s(36). 36**(length-1) converted to base 36 is 10**(length-1), which is the smallest value that has the digit length you want.Procurator
@EricHu You're solution also produce tokens longer than length.Everick
Here is the version always producing tokens of the desired length: (36**(length-1) + rand(36**length - 36**(length-1))).to_s(36)Everick
Yep, +1 for brevity and lack of dependencies.Quetzalcoatl
@BigBourin Your solution doesnt always produce tokens of desired lengthSulky
This spits out an error for me in Rails 4 and Ruby 2.1.1: NameError: undefined local variable or method length' for main:Object`Corcyra
This will give you an exact length: Random.new.rand( 36**(length-1) ... 36**length ).to_s( 36 )Litigant
If you need speed and exact length, you can also use this, which runs about 20x faster than the one in my last comment: ( rand * (36**length - 36**(length-1) ) + 36**(length-1) ).floor.to_s( 36 ). It's not as readable though, so I would put it in a function. Probably the simplest would be to generate something that is guaranteed to be to long, and then truncate it.Litigant
I'm using this to generate 6 char tokens in a very simple way: rand(36**11).to_s(36).upcase[0,6] i.e. just make the original string safely long enough and then take any N chars from itSestos
@JanKlimo That's just wrong though. rand(36**11).to_s(36) may return a string with only 1 character. There are ways of doing this that are provably correct and will work 100% of the time, and there is your way, which may randomly produce fewer digits than is required. Don't do that. Don't write code that is probabilistically correct, when guaranteed correct is so trivial.Sevik
K
177

This solution generates a string of easily readable characters for activation codes; I didn't want people confusing 8 with B, 1 with I, 0 with O, L with 1, etc.

# Generates a random string from a set of easily readable characters
def generate_activation_code(size = 6)
  charset = %w{ 2 3 4 6 7 9 A C D E F G H J K M N P Q R T V W X Y Z}
  (0...size).map{ charset.to_a[rand(charset.size)] }.join
end
Kynthia answered 17/9, 2008 at 22:24 Comment(7)
Couldn't you just use charset.sample instead of charset.to_a[rand(charset.size)?Hack
@Tom, if you are using Ruby 1.9 or Rails then yes. Plain 1.8.7 then no.Wingard
Is 'U' ambiguous or is that a typo?Wingard
@Wingard - Yep. U and V are ambigvovs.Caralie
@Caralie V's in there though.Wingard
To be secure you would also want to use SecureRandom.random_number(charset.size) instead of rand(charset.size)Wingard
I was just trying to improve on this, by adding lower case and/or some special characters (shift+num), and got the following lists: %w{ A C D E F G H J K L M N P Q R T W X Y Z 2 3 4 6 7 9 ! @ # $ % ^ & * +} and %w{ A D E F G H J L N Q R T Y a d e f h n r y 2 3 4 7 ! @ # $ % ^ & * } (first list doesn't include lower case, but it's longer) Kinda interesting how that worked out.Spanner
A
141

Since Ruby 2.5, it's really easy with SecureRandom.alphanumeric:

len = 8
SecureRandom.alphanumeric(len)
=> "larHSsgL"

It generates random strings containing A-Z, a-z and 0-9 and therefore should be applicable in most use-cases. And they are generated randomly secure, which might be a benefit, too.


This is a benchmark to compare it with the solution having the most upvotes:

require 'benchmark'
require 'securerandom'

len = 10
n = 100_000

Benchmark.bm(12) do |x|
  x.report('SecureRandom') { n.times { SecureRandom.alphanumeric(len) } }
  x.report('rand') do
    o = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten
    n.times { (0...len).map { o[rand(o.length)] }.join }
  end
end

                   user     system      total        real
SecureRandom   0.429442   0.002746   0.432188 (  0.432705)
rand           0.306650   0.000716   0.307366 (  0.307745)

So the rand solution only takes about 3/4 of the time of SecureRandom. That might matter if you generate a lot of strings, but if you just create some random string from time to time I'd always go with the more secure implementation since it is also easier to call and more explicit.

Anthracite answered 17/9, 2008 at 22:24 Comment(1)
Now we need a similar solution for 0-z (without the uppercase characters). SecureRandom doesn't seem to provide that out of the box.Tse
M
136

Others have mentioned something similar, but this uses the URL safe function.

require 'securerandom'
p SecureRandom.urlsafe_base64(5) #=> "UtM7aa8"
p SecureRandom.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"
p SecureRandom.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ=="

The result may contain A-Z, a-z, 0-9, “-” and “_”. “=” is also used if padding is true.

Mealie answered 17/9, 2008 at 22:24 Comment(0)
M
55
[*('A'..'Z')].sample(8).join

Generate a random 8 letter string (e.g. NVAYXHGR)

([*('A'..'Z'),*('0'..'9')]-%w(0 1 I O)).sample(8).join

Generate a random 8 character string (e.g. 3PH4SWF2), excludes 0/1/I/O. Ruby 1.9

Magus answered 17/9, 2008 at 22:24 Comment(4)
Only problem is each character in the result is unique. Limits the possible values.Triphammer
If this feature request goes through, Ruby 1.9.x may end up with #sample for sampling without replacment and #choice for sampling with replacement.Porphyroid
This is an error, i think you need ... [*("A".."Z")]' ; ((not single qoutes))Ahola
can you tell me how i can stub this for rspec to pass.Haff
M
31

I can't remember where I found this, but it seems like the best and the least process intensive to me:

def random_string(length=10)
  chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ0123456789'
  password = ''
  length.times { password << chars[rand(chars.size)] }
  password
end
Mealie answered 17/9, 2008 at 22:24 Comment(5)
Perhaps you found it here? travisonrails.com/2007/06/07/generate-random-text-with-rubySoulsearching
Isn't there a 0 and 1 missing from this?Fjord
Looks like that could be where I found it.Sulphate
And ya, it does look like 0 and 1 are missing.Sulphate
The 0 and 1 and O and I were intentionally missing because those characters are ambiguous. If this sort of code is being used to generate a set of characters that a user needs to copy, it's best to exclude characters that may be difficult to distinguish out of context.Belly
W
29
require 'securerandom'
SecureRandom.urlsafe_base64(9)
Wilmoth answered 17/9, 2008 at 22:24 Comment(2)
old skool (and uglier) alternative: Random.new.bytes(9)Telpherage
btw, urlsafe_base64 returns a string about 4/3 the length indicated. To get a string exactly n chars long, try n=9 ; SecureRandom.urlsafe_base64(n)[0..n-1]Telpherage
O
26

If you want a string of specified length, use:

require 'securerandom'
randomstring = SecureRandom.hex(n)

It will generate a random string of length 2n containing 0-9 and a-f

Occlude answered 17/9, 2008 at 22:24 Comment(2)
It doesn't generate a string of length n, it actually generates a string that's 4/3 of n.Foreshorten
@OmarAli you're wrong. As per Ruby documentation in case of .hex it's 2n. Documentation: SecureRandom.hex generates a random hexadecimal string. The argument n specifies the length, in bytes, of the random number to be generated. The length of the resulting hexadecimal string is twice of n.Darrelldarrelle
H
15

Array.new(n){[*"0".."9"].sample}.join, where n=8 in your case.

Generalized: Array.new(n){[*"A".."Z", *"0".."9"].sample}.join, etc.

From: "Generate pseudo random string A-Z, 0-9".

Hexarchy answered 17/9, 2008 at 22:24 Comment(0)
I
11

Here is one line simple code for random string with length 8:

 random_string = ('0'..'z').to_a.shuffle.first(8).join

You can also use it for random password having length 8:

random_password = ('0'..'z').to_a.shuffle.first(8).join
Isoclinal answered 17/9, 2008 at 22:24 Comment(1)
You should not use this to generate yourself a password, as this method will never repeat a character. Therefore you're only using P(36, 8) / 36^8 = 0.4 of the possible character space for 8 characters (~2x easier to brute force) or P(36, 25) / 36^25 = 0.00001 of the possible character space for 25 characters (~100,000x easier to brute force).Nostrum
D
11

Ruby 1.9+:

ALPHABET = ('a'..'z').to_a
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]

10.times.map { ALPHABET.sample }.join
#=> "stkbssowre"

# or

10.times.inject('') { |s| s + ALPHABET.sample }
#=> "fdgvacnxhc"
Devindevina answered 17/9, 2008 at 22:24 Comment(4)
The map solution is really nice!Najera
You can ask #sample for how many elements you want. E.g. ALPHABET.sample(10).join... ruby-doc.org/core-2.4.0/Array.html#method-i-sampleCircumcision
That is actually wrong. sample(10) gives you 10 unique samples. But you want to allow them to repeat. But I would use Array.new(10).map for performanceAppendicle
I wanted alphanumeric with both lower and upper cases. I've also switched to use Array.new and its block syntax. Array.new(20) { [*'0'..'9', *'a'..'z', *'A'..'Z'].sample }.joinClergy
C
11
require 'sha1'
srand
seed = "--#{rand(10000)}--#{Time.now}--"
Digest::SHA1.hexdigest(seed)[0,8]
Coccyx answered 17/9, 2008 at 22:24 Comment(3)
Interesting, but quite a bit more computationally expensiveEcclesiastes
Also no capacity for limited scope of characters.Housing
Keep in mind that a hex digest returns only 0-9 and a-f characters.Seleucia
S
8

Be aware: rand is predictable for an attacker and therefore probably insecure. You should definitely use SecureRandom if this is for generating passwords. I use something like this:

length = 10
characters = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a

password = SecureRandom.random_bytes(length).each_char.map do |char|
  characters[(char.ord % characters.length)]
end.join
Sopping answered 17/9, 2008 at 22:24 Comment(1)
This is probably the "most" secure solution. SecureRandom attempts to use underlying security APIs provided by the operating system. If you have OpenSSL it will use that, if you're on Windows it will go the best option there. I especially like this solution because it allows you to specify a set of characters for use. Though it won't work if your character set is longer than the maximum value of a byte: 255. I recommend viewing the source code for SecureRandom in the doc: ruby-doc.org/stdlib-1.9.3/libdoc/securerandom/rdoc/…Listing
E
8

Another method I like to use:

 rand(2**256).to_s(36)[0..7]

Add ljust if you are really paranoid about the correct string length:

 rand(2**256).to_s(36).ljust(8,'a')[0..7]
Eudy answered 17/9, 2008 at 22:24 Comment(1)
Even better to grab the least significant part of the random number by using the right hand side of the string: rand(2**64).to_s(36)[-10,10]Ecclesiastes
W
7

Here is one simple code for random password with length 8:

rand_password=('0'..'z').to_a.shuffle.first(8).join
Wakerobin answered 17/9, 2008 at 22:24 Comment(0)
R
7
SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')

Something from Devise

Ratliff answered 17/9, 2008 at 22:24 Comment(3)
Why is it replacing string characters using .tr('+/=lIO0', 'pqrsxyz')?Mccandless
The special characters because they are not URL safe. And l/I or O/0 because they are very easy confused if you use the technique to generate readable user passwords.Arminius
That function has a bit of bias towards certain characters. Also for other lengths (e.g. 16), the last character will not be random. Here's a way to avoid that. SecureRandom.base64(64).tr('+/=lIO01', '')[0,16]Khartoum
B
7

I think this is a nice balance of conciseness, clarity and ease of modification.

characters = ('a'..'z').to_a + ('A'..'Z').to_a
# Prior to 1.9, use .choice, not .sample
(0..8).map{characters.sample}.join

Easily modified

For example, including digits:

characters = ('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a

Uppercase hexadecimal:

characters = ('A'..'F').to_a + (0..9).to_a

For a truly impressive array of characters:

characters = (32..126).to_a.pack('U*').chars.to_a
Bibliotheca answered 17/9, 2008 at 22:24 Comment(1)
i would recommend this to just use capital letters + numbers, also remove the "confusing" ones charset = (1..9).to_a.concat(('A'..'Z').to_a).reject{ |a| [0, 1, 'O', 'I'].include?(a) } (0...size).map{ charset[rand(charset.size)] }.joinMascle
A
5

You can use String#random from the Facets of Ruby Gem facets.

It basically does this:

class String
  def self.random(len=32, character_set = ["A".."Z", "a".."z", "0".."9"])
    characters = character_set.map { |i| i.to_a }.flatten
    characters_len = characters.length
    (0...len).map{ characters[rand(characters_len)] }.join
  end
end
Aught answered 17/9, 2008 at 22:24 Comment(0)
Z
5

Just adding my cents here...

def random_string(length = 8)
  rand(32**length).to_s(32)
end
Zany answered 17/9, 2008 at 22:24 Comment(1)
NB: this doesn't always return a string exactly +length+ long - it may be shorter. It depends on the the number returned by randTelpherage
M
4

This solution needs external dependency, but seems prettier than another.

  1. Install gem faker
  2. Faker::Lorem.characters(10) # => "ang9cbhoa8"
Myeloid answered 17/9, 2008 at 22:24 Comment(1)
Passing number with the 1st argument of characters is deprecated. Use keyword argument like characters(number: ...) instead. Faker::Lorem.characters(number: 10) # => "ang9cbhoa8"Westering
A
4

Given:

chars = [*('a'..'z'),*('0'..'9')].flatten

Single expression, can be passed as an argument, allows duplicate characters:

Array.new(len) { chars.sample }.join
Abstinence answered 17/9, 2008 at 22:24 Comment(0)
S
4

I was doing something like this recently to generate an 8 byte random string from 62 characters. The characters were 0-9,a-z,A-Z. I had an array of them as was looping 8 times and picking a random value out of the array. This was inside a Rails app.

str = ''
8.times {|i| str << ARRAY_OF_POSSIBLE_VALUES[rand(SIZE_OF_ARRAY_OF_POSSIBLE_VALUES)] }

The weird thing is that I got good number of duplicates. Now randomly this should pretty much never happen. 62^8 is huge, but out of 1200 or so codes in the db i had a good number of duplicates. I noticed them happening on hour boundaries of each other. In other words I might see a duple at 12:12:23 and 2:12:22 or something like that...not sure if time is the issue or not.

This code was in the before create of an ActiveRecord object. Before the record was created this code would run and generate the 'unique' code. Entries in the DB were always produced reliably, but the code (str in the above line) was being duplicated much too often.

I created a script to run through 100000 iterations of this above line with small delay so it would take 3-4 hours hoping to see some kind of repeat pattern on an hourly basis, but saw nothing. I have no idea why this was happening in my Rails app.

Steno answered 17/9, 2008 at 22:24 Comment(0)
B
3

Another trick that works with Ruby 1.8+ and is fast is:

>> require "openssl"
>> OpenSSL::Random.random_bytes(20).unpack('H*').join
=> "2f3ff53dd712ba2303a573d9f9a8c1dbc1942d28"

It get's you random hex string. Similar way you should be able to generate base64 string ('M*').

Baskerville answered 17/9, 2008 at 22:24 Comment(0)
S
3

My favorite is (:A..:Z).to_a.shuffle[0,8].join. Note that shuffle requires Ruby > 1.9.

Semitropical answered 17/9, 2008 at 22:24 Comment(0)
T
3

My 2 cents:

  def token(length=16)
    chars = [*('A'..'Z'), *('a'..'z'), *(0..9)]
    (0..length).map {chars.sample}.join
  end
Triphammer answered 17/9, 2008 at 22:24 Comment(0)
T
3

2 solutions for a random string consisting of 3 ranges:

(('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a).sample(8).join

([*(48..57),*(65..90),*(97..122)]).sample(8).collect(&:chr)*""

One Character from each Range.

And if you need at least one character from each range, such as creating a random password that has one uppercase, one lowercase letter and one digit, you can do something like this:

( ('a'..'z').to_a.sample(8) + ('A'..'Z').to_a.sample(8) + (0..9).to_a.sample(8) ).shuffle.join 
#=> "Kc5zOGtM0H796QgPp8u2Sxo1"
Tabu answered 17/9, 2008 at 22:24 Comment(1)
And if you want to enforce a certain number of each range, you could do something like this: ( ('a'..'z').to_a.sample(8) + ('A'..'Z').to_a.sample(8) + (0..9).to_a.sample(8) + [ "%", "!", "*" ].sample(8) ).shuffle.join #=> "Kc5zOGtM0*H796QgPp%!8u2Sxo1"Nagle
S
3

I like Radar's answer best, so far, I think. I'd tweak a bit like this:

CHARS = ('a'..'z').to_a + ('A'..'Z').to_a
def rand_string(length=8)
  s=''
  length.times{ s << CHARS[rand(CHARS.length)] }
  s
end
Seleucia answered 17/9, 2008 at 22:24 Comment(3)
Why not use (CHARS*length).sample(length).join?Tub
@Tub This suggestion would generate a weighted string, in favour of non-repeated characters. For example, if CHARS=['a','b'] then your method would generate "aa" or "bb" only 33% of the time, but "ab" or "ba" 67% of the time. Maybe that's not a problem, but it's worth bearing in mind!Raw
good point, @TomLord, I think I didn't actually realise that when I posted that suggestion (although I must admit I don't remember posting that at all :D)Tub
E
3

With this method you can pass in an abitrary length. It's set as a default as 6.

def generate_random_string(length=6)
  string = ""
  chars = ("A".."Z").to_a
  length.times do
    string << chars[rand(chars.length-1)]
  end
  string
end
Egwin answered 17/9, 2008 at 22:24 Comment(0)
N
2
''.tap {|v| 4.times { v << ('a'..'z').to_a.sample} }
Nason answered 17/9, 2008 at 22:24 Comment(0)
D
2

Here is another method:

  • It uses the secure random number generator instead of rand()
  • Can be used in URLs and file names
  • Contains uppercase, lowercase characters and numbers
  • Has an option not to include ambiguous characters I0l01

Needs require "securerandom"

def secure_random_string(length = 32, non_ambiguous = false)
  characters = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a

  %w{I O l 0 1}.each{ |ambiguous_character| 
    characters.delete ambiguous_character 
  } if non_ambiguous

  (0...length).map{
    characters[ActiveSupport::SecureRandom.random_number(characters.size)]
  }.join
end
Denbighshire answered 17/9, 2008 at 22:24 Comment(0)
B
2

We've been using this on our code:

class String

  def self.random(length=10)
    ('a'..'z').sort_by {rand}[0,length].join
  end

end

The maximum length supported is 25 (we're only using it with the default anyway, so hasn't been a problem).

Someone mentioned that 'a'..'z' is suboptimal if you want to completely avoid generating offensive words. One of the ideas we had was removing vowels, but you still end up with WTFBBQ etc.

Bedaub answered 17/9, 2008 at 22:24 Comment(2)
Your approach can't return repeating characters (e.g, uuunMoBldj)... Is this what's wanted?Seleucia
Yes, I suppose that technically isn't really a random string anymore, good find webmat.Ecclesiastes
L
1

Here is a improve of @Travis R answer:

 def random_string(length=5)
    chars = 'abdefghjkmnpqrstuvwxyzABDEFGHJKLMNPQRSTUVWXYZ'
    numbers = '0123456789'
    random_s = ''
    (length/2).times { random_s << numbers[rand(numbers.size)] }
    (length - random_s.length).times { random_s << chars[rand(chars.size)] }
    random_s.split('').shuffle.join
  end

At @Travis R answer chars and numbers were together, so sometimes random_string could return only numbers or only characters. With this improve at least half of random_string will be characters and the rest are numbers. Just in case if you need a random string with numbers and characters

Loess answered 17/9, 2008 at 22:24 Comment(0)
L
1

For devise secure_validatable you can use this

(0...8).map { ([65, 97].sample + rand(26)).chr }.push(rand(99)).join
Lauzon answered 17/9, 2008 at 22:24 Comment(1)
To generate a random password using uppercase, lowercase, numbers, and special characters, I used something like: random_ascii_character = -> { (33..126).to_a.sample.chr }; 8.times.map { random_ascii_character.call }.shuffle.joinAry
B
1

If you are on a UNIX and you still must use Ruby 1.8 (no SecureRandom) without Rails, you can also use this:

random_string = `openssl rand -base64 24`

Note this spawns new shell, this is very slow and it can only be recommended for scripts.

Baskerville answered 17/9, 2008 at 22:24 Comment(0)
B
1

This is based on a few other answers, but it adds a bit more complexity:

def random_password
  specials = ((32..47).to_a + (58..64).to_a + (91..96).to_a + (123..126).to_a).pack('U*').chars.to_a
  numbers  = (0..9).to_a
  alpha    = ('a'..'z').to_a + ('A'..'Z').to_a
  %w{i I l L 1 O o 0}.each{ |ambiguous_character| 
    alpha.delete ambiguous_character 
  }
  characters = (alpha + specials + numbers)
  password = Random.new.rand(8..18).times.map{characters.sample}
  password << specials.sample unless password.join =~ Regexp.new(Regexp.escape(specials.join))
  password << numbers.sample  unless password.join =~ Regexp.new(Regexp.escape(numbers.join))
  password.shuffle.join
end

Essentially it ensures a password that is 8 - 20 characters in length, and which contains at least one number and one special character.

Brechtel answered 17/9, 2008 at 22:24 Comment(0)
P
1

try this out

def rand_name(len=9)
  ary = [('0'..'9').to_a, ('a'..'z').to_a, ('A'..'Z').to_a]
  name = ''

  len.times do
    name << ary.choice.choice
  end
  name
end

I love the answers of the thread, have been very helpful, indeed!, but if I may say, none of them satisfies my ayes, maybe is the rand() method. it's just doesn't seems right to me, since we've got the Array#choice method for that matter.

Pontoon answered 17/9, 2008 at 22:24 Comment(1)
ar = [('0'..'9').to_a, ('a'..'z').to_a, ('A'..'Z').to_a].flatten; 10.times.map { ar.choice }.joinSevik
P
0
a='';8.times{a<<[*'a'..'z'].sample};p a

or

8.times.collect{[*'a'..'z'].sample}.join
Psychodiagnostics answered 17/9, 2008 at 22:24 Comment(0)
H
0
10.times do 
  alphabet = ('a'..'z').to_a
  string += alpha[rand(alpha.length)]
end
Hirohito answered 17/9, 2008 at 22:24 Comment(0)
L
0
Array.new(8).inject(""){|r|r<<('0'..'z').to_a.shuffle[0]}  # 57
(1..8).inject(""){|r|r<<('0'..'z').to_a.shuffle[0]}        # 51
e="";8.times{e<<('0'..'z').to_a.shuffle[0]};e              # 45
(1..8).map{('0'..'z').to_a.shuffle[0]}.join                # 43
(1..8).map{rand(49..122).chr}.join                         # 34
Lamina answered 17/9, 2008 at 22:24 Comment(0)
L
0

Here's a solution that is flexible and allows dups:

class String
  # generate a random string of length n using current string as the source of characters
  def random(n)
    return "" if n <= 0
    (chars * (n / length + 1)).shuffle[0..n-1].join  
  end
end

Example:

"ATCG".random(8) => "CGTGAAGA"

You can also allow a certain character to appear more frequently:

"AAAAATCG".random(10) => "CTGAAAAAGC"

Explanation: The method above takes the chars of a given string and generates a big enough array. It then shuffles it, takes the first n items, then joins them.

Latham answered 17/9, 2008 at 22:24 Comment(0)
D
0

Create an empty string or a pre-fix if require:

myStr = "OID-"

Use this code to populate the string with random numbers:

begin; n = ((rand * 43) + 47).ceil; myStr << n.chr if !(58..64).include?(n); end while(myStr.length < 12)

Notes:

(rand * 43) + 47).ceil

It will generate random numbers from 48-91 (0,1,2..Y,Z)

!(58..64).include?(n)

It is used to skip special characters (as I am not interested to include them)

while(myStr.length < 12)

It will generate total 12 characters long string including prefix.

Sample Output:

"OID-XZ2J32XM"
Duprey answered 17/9, 2008 at 22:24 Comment(0)
F
0

To make your first into one statement:

(0...8).collect { |n| value  << (65 + rand(25)).chr }.join()
Foreclose answered 17/9, 2008 at 22:24 Comment(0)
S
-1

In ruby 1.9 one can use Array's choice method which returns random element from array

Seltzer answered 17/9, 2008 at 22:24 Comment(1)
Thanks for the heads up - however, svn.ruby-lang.org/repos/ruby/tags/v1_9_1_0/NEWS seems to indicate that Array#sample is to be used in 1.9.1, not Array#choose / choiceBretbretagne
I
-1

This is almost as ugly but perhaps as step in right direction?

 (1..8).map{|i| ('a'..'z').to_a[rand(26)]}.join
Imagination answered 17/9, 2008 at 22:24 Comment(1)
:) I actually love the fastest gun in a perverse wayImagination

© 2022 - 2024 — McMap. All rights reserved.