From ActiveRecord to Raw SQL

ActiveRecord is awesome. Its dynamic finder methods, scopes, and effortless joins make working with a database a snap. But there are always those times when writing raw sql makes more sense. In my case, I was joining across several tables, and then sorting by a value from the joined table. The problem was that my views were written to handle an ActiveRecord object, not a simple hash, which is what a find_by_sql query returns.

Before:

#controller:
@posts = Post.find(:all)
 
#view:
< % @posts.each do |post| %>
   < %= post.title %>
   < %= post.body %>
   < %= post.author %>
   < %= post.date %>
< % end %>

After:

#controller:
@posts = ActiveRecord::Base.connection.execute("SELECT * FROM posts")
# @posts.class #=> Hash
# @posts[0].title will return a method missing error

For the view you have two choices.
1)Rewrite the method calls:

< % @posts.each do |post| %>
    < %= post['title'] %>
    < %= post['body'] %>
   < %= post['author'] %>
    < %= post['date'] %>
&lt; % end %&gt;
#this is tedious

or
2) Hack the hash to make keys act as methods. There are several ways to do this. From a stackoverflow question I asked, someone showed me a module in the Ruby standard library called OpenStruct:

#controller
require 'ostruct'
@posts = OpenStruct.new(ActiveRecord::Base.connection.execute("SELECT * FROM posts"))
 
#view
< % @posts.each do |post| %>
   < %= post.title %>
   < %= post.body %>
   < %= post.author %>
   < %= post.date %>
< % end %>

Other ways to do this (from stackoverflow answerers):

  • define the method_missing function for the Hash class
  • define the method_missing function for just the hash instance you’re dealing with

You can try these out with a really cool pastebin that actually runs the code:

It’s interesting that all of these solutions used about 5mb of memory according to the ideone.com.

Comments 2

  1. Andrew wrote:

    Hi,

    What version of Rails are you using? I’ve never known find_by_sql to return anything other than an array of model objects. I don’t even see how it’s possible @posts could be a hash as they would be unordered and you also subsequently access it via an integer key (which is possible but unusual for a Hash).

    Regards,
    Andrew

    Posted 27 Jan 2010 at 7:46 am
  2. admin wrote:

    2.3.3–but that shouldn’t have anything to do with it.

    You’re absolutely right, any finder method will return an array of objects. And you’re right again about find_by_sql. I was probably thinking of ActiveRecord::Base.connection.execute(’SELECT * FROM users’) (source=http://www.rabbitcreative.com/2007/06/08/executing-raw-sql-in-rails/) which returns mysql objects whose attributes are returned using a hash.

    But thanks for the correction–I updated the post.

    Posted 28 Jan 2010 at 1:10 am

Post a Comment

Your email is never published nor shared. Required fields are marked *