Random Records in Rails

December 4th, 2007 by Trevor

There are a number of different ways to retrieve random items from a database in Rails, most of which have been discussed at length on the Rails wiki. According to this article, there are 3 preferred methods:

  1. 1. Select a Record by Random Offset
  2. 2. Randomize with the Database
  3. 3. Randomize with Ruby

For an in-depth look into these options, you can peruse this discussion page, which details some of the pros and cons of different strategies for randomization.

After trying out a few of the techniques that abound in this area, I stumbled across an article from Jamis Buck, where he discusses a RESTful way to approach the creation of custom finders. Although randomization isn't the focus of the article, he does provide a bit of guidance in that regard. His strategy uses Ruby for the randomization, and employs 2 light-weight queries. The first query gathers a list of valid ids. The second simply selects a single item using that (randomized) id.

So, I did a bit of cargo-culting and repurposing to achieve an efficient, database agnostic way to retrieve random items from a database using ActiveRecord. Simply add the following to the model from which you'd like to be able to pull random records:

class Widget < ActiveRecord::Base
 
  # ...
 
  def self.random
    ids = connection.select_all("SELECT id FROM widgets")
    find(ids[rand(ids.length)]["id"].to_i) unless ids.blank?
  end
 
end

Then, you can use the following bit of code in a controller like so:

class SomeController < ApplicationController
 
  # ...
 
  def some_action
    @widget = Widget.random
  end
 
end

And you've got a small and efficient "random finder" for use throughout your app. Lovely.

Comments

Posting code? Please use Pastie.

Have a question? Please visit the Forum.

6 Comments

  1. It could be me, but isn’t retrieving _all_ ID’s from the database and only using one the opposite of efficient?

    I have no clue how to do this in a more efficient way, but that’s just my 0.02$ :)


  2. I’ve heard that this may not be the most efficient way if you have lot of data, but it seems to work well enough for me, and it’s database agnostic, which I like.

    Comment by Trevor on July 16, 2008

  3. That’s definitely not going to fly for large data sets.

    Comment by Preston Lee on August 5, 2008

  4. I’ve heard that before, but I have yet to see a better solution :)

    Comment by Trevor on August 5, 2008

  5. Not to add to the fracas (I agree large sets could be unwieldy), but this is a little more agnostic:

    def self.random_50
    all_ids = find(:all, :select => :id)
    random_ids = 50.times.collect{|t| rand(all_ids.length)}
    find(random_ids)
    end

    You could end up with repeats, but so can your method.

    Comment by Karl on August 28, 2008

  6. Opps, sorry, copied an older version. This is much better:

    def self.random_50
    all_ids =find( :all, :select => :id )
    random_ids = 50.times.collect{ |t| all_ids[rand(all_ids.length)].id }
    find(random_ids)
    end

    Comment by Karl on August 28, 2008