Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby rand() cannot accept variables?

I'm a little baffled by this.

My end goal in a RoR project is to grab a single random profile from my database.

I was thinking it would be something like:

@profile = Profile.find_by_user_id(rand(User.count))

It kept throwing an error because user_id 0 doesn't exist, so I pulled parts of it out just to check out what's going on:

@r = rand(User.count)

<%= @r %>

This returns 0 every time. So what's going on? I registered 5 fake users and 5 related profiles to test this.

If I take Profile.find_by_user_id(rand(User.count)) and rewrite it as

Profile.find_by_user_id(3)

it works just fine.

User.count is working too. So I think that rand() can't take an input other than a static integer.

Am I right? Whats going on?

like image 463
Elxx Avatar asked Dec 02 '10 04:12

Elxx


2 Answers

Try:

Profile.first(:offset => rand(Profile.count))

As a database ages, especially one with user records, there will be gaps in your ID field sequence. Trying to grab an ID at random will have a potential to fail because you might try to randomly grab one that was deleted.

Instead, if you count the number of records, then randomly go to some offset into the table you will sidestep the possibility of having missing IDs, and only be landing on existing records.

The following example from the OPs question could run into some problems unless the integrity of the database is watched very carefully:

profile = Profile.find_by_user_id(rand(User.count))

The problem is, there's a possibility for the User table to be out of sync with the Profile table, allowing them to have different numbers of records. For instance, if User.count was 3 and there were two records in Profile there's potential for a failed lookup resulting in an exception.

like image 115
the Tin Man Avatar answered Oct 01 '22 00:10

the Tin Man


I'm not sure why rand(i) isn't working as you expect (it works fine for me), but this isn't a good way to find a random profile regardless; if a profile is ever deleted, or there are any users without profiles, then this will fail.

I don't think there's an efficient way to do this in Rails using ActiveRecord. For a small number of users, you could just do Profile.find_all() and select a random profile from that array, but you'd probably be better off doing something like

@profile = Profile.find_by_sql("SELECT * FROM profiles ORDER BY RAND() LIMIT 1").first

There are many other questions on StackOverflow about how to select a random record in SQL; I'd say this is the easiest, but if you're concerned about efficiency then have a look around and see if there's another implementation you like better.

EDIT: find_by_sql returns an array, so you need to do .first to get a single Profile.

like image 28
bnaul Avatar answered Sep 30 '22 23:09

bnaul