18

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.

shamittomar
  • 46,210
  • 12
  • 74
  • 78
JP Silvashy
  • 46,977
  • 48
  • 149
  • 227

8 Answers8

39

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.

Jorge Israel Peña
  • 36,800
  • 16
  • 93
  • 123
7

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")
Dan Park
  • 5
  • 2
Ben Hutchison
  • 2,433
  • 2
  • 21
  • 25
  • 1
    The last line is a little too verbose. This does the same thing (in Rails 3): `Model.where("id >= #{random_id}").first` – Nate Bird Nov 16 '11 at 02:10
  • 3
    If any records have been deleted, then method does not generate uniformly distributed results, which is generally what one would expect. Imagine the case where ids 1-10 exist, except for id 5. In that case, the model with an id of 6 would be returned when the random number generator produces either a 5 or a 6 (20% of the time), whereas each of the other existing ids is selected only 10% of the time. Perhaps not a deal breaker, but something of which to be aware. – encoded Mar 05 '13 at 05:19
6

Try using Array's sample method:

@user = User.all.sample(1)
kushpatel
  • 199
  • 2
  • 6
  • I like your answer that's some real ruby style – blawzoo Oct 19 '12 at 15:04
  • You should remove the (1), as this results in a singleton array. Using User.all.sample results in just one user – Danny Mar 22 '14 at 17:27
  • 3
    You definitely should not do this if you have a significant amount of data - it will attempt to load all users in memory. If you're on Rails 4 and Postgres, use `User.order("RANDOM()").limit(10)` (from http://stackoverflow.com/a/17373279/1298553). – cgenco Nov 21 '14 at 02:27
4

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
joost
  • 6,549
  • 2
  • 31
  • 36
4

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.

aNoble
  • 7,033
  • 2
  • 37
  • 33
2

Strongly Recommend this gem for random records, which is specially designed for table with lots of data rows:

https://github.com/haopingfan/quick_random_records

Simple Usage:

@user = User.random_records(1).take


All other answers perform badly with large database, except this gem:

  1. quick_random_records only cost 4.6ms totally.

enter image description here

  1. the accepted answer User.order('RAND()').limit(10) cost 733.0ms.

enter image description here

  1. the offset approach cost 245.4ms totally.

enter image description here

  1. the User.all.sample(10) approach cost 573.4ms.

enter image description here

Note: My table only has 120,000 users. The more records you have, the more enormous the difference of performance will be.


UPDATE:

Perform on table with 550,000 rows

  1. Model.where(id: Model.pluck(:id).sample(10)) cost 1384.0ms

enter image description here

  1. gem: quick_random_records only cost 6.4ms totally

enter image description here

Derek Fan
  • 817
  • 11
  • 10
2

If you would need a random record but only within certain criteria you could use "random_where" from this code:

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

    def self.random_where(*params)
      if (c = where(*params).count) != 0
        where(*params).find(:first, :offset =>rand(c))
      end
    end

  end
end

For e.g :

@user = User.random_where("active = 1")

This function is very useful for displaying random products based on some additional criteria

Pawel Barcik
  • 519
  • 6
  • 9
  • Rails 3 and active relation chaining takes care of this case. You could do just `User.random.where('active = 1')` without the need for an additional global method. – Nate Bird Nov 15 '11 at 19:25
  • 1
    "find" method doesn't return Relation object, it returns either model object or an array :) , but! you could do this `User.where('active = 1').random` and this will work just fine... I don't know why I missed it :) – Pawel Barcik Nov 15 '11 at 23:35
  • You are correct. Good eye. I thought one thing and typed another. :-) – Nate Bird Nov 16 '11 at 02:09
1

Here is the best solution for getting random records from database. RoR provide everything in ease of use.

For getting random records from DB use sample, below is the description for that with example.

Backport of Array#sample based on Marc-Andre Lafortune’s github.com/marcandre/backports/ Returns a random element or n random elements from the array. If the array is empty and n is nil, returns nil. If n is passed and its value is less than 0, it raises an ArgumentError exception. If the value of n is equal or greater than 0 it returns [].

[1,2,3,4,5,6].sample     # => 4     
[1,2,3,4,5,6].sample(3)  # => [2, 4, 5]     
[1,2,3,4,5,6].sample(-3) # => ArgumentError: negative array size     
[].sample     # => nil     
[].sample(3)  # => []     

You can use condition with as per your requirement like below example.

User.where(active: true).sample(5)

it will return randomly 5 active user's from User table

For more help please visit : http://apidock.com/rails/Array/sample

hgsongra
  • 1,454
  • 15
  • 25