Hi, I'm Samuel Cochran

on Twitter, Facebook, Google, LinkedIn, GitHub, Stack Overflow, and Steam.

Predictable Random Elements from ActiveRecord

Say you want to get some random examples to display, but you want those examples to be the same for each user. Ruby doesn't really make it easy to seed a random number generator, so instead we can use a hash digest:

Digest::MD5.hexdigest("bob@example.com")
=> "4b9bb80620f03eb3719e0a061c14283d"

This gives a predictably unique(ish) result for each unique input, so each user's email or username should be sufficient. The hex representation isn't all that useful though. If we can generate full bytes then we can chop off some bytes as unsigned numbers...

Digest::MD5.digest("bob@example.com")
=> "K\x9B\xB8\x06 \xF0>\xB3q\x9E\n\x06\x1C\x14(="

Cool! Let's run the unpack method over this bad boy:

Digest::MD5.digest("bob@example.com").unpack "C*"
=> [75, 155, 184, 6, 32, 240, 62, 179, 113, 158, 10, 6, 28, 20, 40, 61]

We get some few 8-bit unsigned integers. If 255 isn't big enough for you, you can grab 16, 32 or even 64-bit unsigned integers instead, but we'll get fewer:

Digest::MD5.digest("bob@example.com").unpack "Q*"
=> [12916024801687542603, 4406794345975029361]

Now we have some numbers we can seed a new random number generator:

random = Random.new Digest::MD5.digest("bob@example.com").unpack("Q").first
=> #<Random:0x007fed619e5060>
random.rand
=> 0.1594236871887449
random.rand
=> 0.03664397704408895

This will always produce the same sequence of random numbers.

If you want to instead just shuffle an array of items, try:

random = Random.new Digest::MD5.digest("bob@example.com").unpack("Q").first
=> #<Random:0x007fed619e5060>
["one", "two", "three"].sort_by { random.rand }
=> ["two", "one", "three"]

There are probably different and better ways to do this, but this way works for me!