Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails select random record

I don't know if I'm just looking in the wrong places here or what, but does active record have a method for retrieving a random object?

Something like?

@user = User.random

Or... well since that method doesn't exist is there some amazing "Rails Way" of doing this, I always seem to be to verbose. I'm using mysql as well.

like image 251
JP Silvashy Avatar asked Sep 04 '10 05:09

JP Silvashy


5 Answers

Most of the examples I've seen that do this end up counting the rows in the table, then generating a random number to choose one. This is because alternatives such as RAND() are inefficient in that they actually get every row and assign them a random number, or so I've read (and are database specific I think).

You can add a method like the one I found here.

module ActiveRecord
  class Base
    def self.random
      if (c = count) != 0
        find(:first, :offset =>rand(c))
      end
    end
  end
end

This will make it so any Model you use has a method called random which works in the way I described above: generates a random number within the count of the rows in the table, then fetches the row associated with that random number. So basically, you're only doing one fetch which is what you probably prefer :)

You can also take a look at this rails plugin.

like image 124
Jorge Israel Peña Avatar answered Oct 21 '22 16:10

Jorge Israel Peña


We found that offsets ran very slowly on MySql for a large table. Instead of using offset like:

model.find(:first, :offset =>rand(c))

...we found the following technique ran more than 10x faster (fixed off by 1):

max_id = Model.maximum("id")
min_id = Model.minimum("id")
id_range = max_id - min_id + 1
random_id = min_id + rand(id_range).to_i
Model.find(:first, :conditions => "id >= #{random_id}", :limit => 1, :order => "id")
like image 35
Ben Hutchison Avatar answered Oct 21 '22 16:10

Ben Hutchison


Try using Array's sample method:

@user = User.all.sample(1)
like image 21
kushpatel Avatar answered Oct 21 '22 15:10

kushpatel


In Rails 4 I would extend ActiveRecord::Relation:

class ActiveRecord::Relation
  def random
    offset(rand(count))
  end
end

This way you can use scopes:

SomeModel.all.random.first # Return one random record
SomeModel.some_scope.another_scope.random.first
like image 4
joost Avatar answered Oct 21 '22 15:10

joost


I'd use a named scope. Just throw this into your User model.

named_scope :random, :order=>'RAND()', :limit=>1

The random function isn't the same in each database though. SQLite and others use RANDOM() but you'll need to use RAND() for MySQL.

If you'd like to be able to grab more than one random row you can try this.

named_scope :random, lambda { |*args| { :order=>'RAND()', :limit=>args[0] || 1 } }

If you call User.random it will default to 1 but you can also call User.random(3) if you want more than one.

like image 3
aNoble Avatar answered Oct 21 '22 15:10

aNoble