UPDATE: This post here does a much better job at explaining how cool the association proxy is. Check it out.

I stumbled onto a feature I didn’t even know Rails had today, simply because it felt like that’s the way it should work. I didn’t even realize the leap of faith until after the test passed and I realized what was going on.

Let’s say you have a model Foo with a class method called get_snazziest. Now let’s say that Bar has_many :foos@

Here is the cool part:

@bar = Bar.new
@bar.foos.get_snazziest

Apparently, when you call a method on an association proxy, Rails looks for class methods in the associated model with the same name. If it finds one, it runs it. But the really cool part is that it will inject the scope into any calls to find.

Lets put this another way. In the app I was just playing with, I have to validate an address by making sure its zip code belongs to it state.

So I had models like:

class State < ActiveRecord::Base
  has_many :zips
end

class Zip < ActiveRecord::Base
  belongs_to :state

  def self.find_by_code(code)
    find :first, 
      :conditions => [
        'start <= :code AND end >= :code', 
        { :code => code }
      ]
  end
end

So I wanted an elegant way to ask the state if it has a specific zip, without writing any more complex SQL. The solution?

class State < ActiveRecord::Base
  has_many :zips

  def has_zip?(code)
    zips.find_by_code code
  end
end

@state = State.find_by_abbrev('CA')
@state.has_zip?('95472') # => true
#SQL =>  SELECT * FROM zips WHERE (zips.state_id = 1) AND (start <= '95472' AND end >= '95472') LIMIT 1

A search for a zip, scoped by a state, easy as pie.

The great thing about Rails is that the API is intelligent enough that you can start to intuitively understand how things may work. When I wrote that code I did not know it would call a class method on Zip but at the same time I just assumed it would work.

Yay for Rails working the way I expect it to without knowing better!

Leave a Reply