October 07, 2006

Posted by John

Older: Overtaken By Fake Comments

Newer: Rails Gets Unicode Handling

Active Record Find Tip

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 Comments

  1. Nick Howell Nick Howell

    Dec 12, 2006

    Might want to check out alias_method_chain:

    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

  2. Nick Howell Nick Howell

    Dec 12, 2006

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

    
      class Thing &amp;lt; ActiveRecord::Base
        class &amp;lt;&amp;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. Its a little more verbose, but wouldn’t this suffice?

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

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

  5. 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. Very good article! Ruby forever :)

Sorry, comments are closed for this article to ease the burden of pruning spam.

About

Authored by John Nunemaker (Noo-neh-maker), a programmer who has fallen deeply in love with Ruby. Learn More.

Projects

Flipper
Release your software more often with fewer problems.
Flip your features.