Skip to navigation

Abusing err.. Using AR::Associations

Published February 15, 2006 - Updated February 15, 2006

In Francois Beausoleil’s recent post on Asymmetric Associations he showed the advantage of going with names and associations that are slightly unconventional (in the convention-over-configuration sense of the word).

Sometimes there is only so far convention can take you.

I think Francois is touching on a bigger issue that some Railer’s might not be aware of: ActiveRecord::Associations can be used to build almost all of your model associations, including the not-so-conventional ones.

For example, I’ve often seen code similar to the following:


class User < ActiveRecord::Base
  has_many :emails

def unread_emails Email.find_by_sql([“select * from #{Email.table_name}# where read = 0 and user_id = ?”, self.id]) end

end

You can accomplish exactly the same thing by adding a second association (oops… now updated to include class_name):


class User < ActiveRecord::Base
  has_many :emails
  has_many :unread_emails, :class_name => 'Email', :condition => 'read = 0'
end

or, alternatively, by bolting on the unread method directly to the has_many association:


class User < ActiveRecord::Base
  has_many :emails do
    def unread; find(:all, :condition => 'read = 0'); end
  end
end

Another great example comes from i2, the app that powers the rails wiki:


class Page < ActiveRecord::Base
  belongs_to :book

has_many :versions, :order => “created_at”, :dependent => true has_one :current_version, :class_name => “Version”, :order => “created_at DESC” def find_or_build_version(number = nil) number ? versions[number.to_i – 1] : versions.build(:body => body) end def body current_version ? current_version.body : nil end def to_param title end

end

There’s a few cool tricks going on in this class, but for now we’ll just look at the current_version statement:

has_one :current_version, :class_name => "Version", :order => "created_at DESC"

Even though the Versions table has potentially thousands of records with the same page_id, the above line pulls out just one.

So why does this work, even though its pointing to a table with many version records?

has_one will take only a single record from the resulting query, similar to a find(:first), using a SQL limit clause.

For more neat tricks have a look at the rest of the i2 codebase — it’s a cool little app.

Archived comments

Comments were previously allowed on articles. Though no new comments are being accepted you can see the old comments below.

  1. Jacek Becela

    Is it possible to use these tricks with has_many :through assosciations in some way? (:class_name is ignored then)

    I want something like this:

    auction.cars
    auction.sold_cars
    auction.not_sold_cars
    and so on. Car is associated with Auction through Exposure join model (Car can be exposed on many auctions).

  2. Tim Lucas

    Good question… I haven’t really tried. Give it a try and report back!

  3. rick

    has_many :through associations are unique, since they read a source association for info. Use :source instead of :class_name or :foreign_key to configure it.

    has_many :sold_cars, :through => :exposures, :source => :car, :conditions => …

Thoughts

toolmantim

I’m Tim Lucas, a user experience developer currently in Sydney Australia.

I occasionally write, snap photos, present on various technical topics, tweet my going-ons, share teh codes and post tidbits to the scrapbook.

Most recently I published Simplifying ticket sales on sydneyoperahouse.com (February 16, 2010)

Work with me via Agency Rainford, or shoot an email to and say hello.

Powered by social networks