Active Record Find Tip

October 7th, 2006

Jamis Buck has come roaring back into blogging since his switch to Mephisto. Yesterday, he released another nice tip on Helping ActiveRecord finders help you. He wanted to have a way to display a random record from the database. At first, he was going to create a new action, but knowing that the action would function primarily the same as show, he thought it better to extend the find method in Active Record like so:

class Thing < ActiveRecord::Base
  def self.find(*args)
    if args.first.to_s == "random" 
      ids = connection.select_all("SELECT id FROM things")
      super(ids[rand(ids.length)]["id"].to_i)
    else
      super
    end
  end
end

Now he could call Thing.find(‘random’) to get a random record from the database. This means his show action can display a random record simply by hitting the thing/random restful route (where random would be passed in as the id). You can read the full article on his blog.

6 Responses to “Active Record Find Tip”

  1. Nick Howell Says:

    Might want to check out alias_method_chain:

    class Thing < ActiveRecord::Base class << self def find_with_random(*args) if args.first.to_s == "random" ids = connection.select_all("SELECT id FROM things") find_without_random(ids[rand(ids.length)]["id"].to_i) else find_without_random(*args) end end alias_method_chain :find, :random end end
  2. Nick Howell Says:

    Wow, that was awful. Sorry about the lack of line breaks; left out a <pre>.

    
      class Thing &lt; ActiveRecord::Base
        class &lt;&lt; self
          def find_with_random(*args)
            if args.first.to_s == "random" 
              ids = connection.select_all("SELECT id FROM things")
              find_without_random(ids[rand(ids.length)]["id"].to_i)
            else
              find_without_random(*args)
            end
          end
          alias_method_chain :find, :random
        end
      end
    
    

    Better?

  3. Nathan Says:

    Its a little more verbose, but wouldn’t this suffice?

    Thing.find(:first, :order => “rand()”)

  4. Balint Says:

    This is quite concise but unfortunately it is not database independent. It is rand() in MySQL and random() in PostgreSQL, for example.

  5. ken Says:
    I don’t like any of these solutions.
    • select_all is horribly inefficient (you want to SELECT all ids from the db when you only need one record?!).
    • SQL doesn’t define a PRNG, so using RAND or RANDOM is not portable.
    • MySQL5, at least, doesn’t seem clever enough to realize that “ORDER BY RAND LIMIT 1” is the same as just taking a single random offset, so it takes 10x as long here.

    I think what you want is simply:

    Thing.find :first, :offset => rand(Thing.count)

  6. autologo Says:

    Very good article! Ruby forever :)

Sorry, comments are closed for this article to ease the burden of pruning spam. If you have any further comments, just send me an email.