All hail recipe 24!

I am of course talking about Rails Recipe No.24 "Adding Behaviour To Your ActiveRecord Associations". It's magical.

In one of my applications there is a relationship from a Feed to a FeedExpression where the expression tracks the degree to which that feed associates with a concept (actually a value). In Rails that's a pretty typical situation that you handle with has_many, so:

class Feed < ActiveRecord::Base
    has_many :feed_expressions
end

Now I can refer to:

feed.feed_expressions

to process the expressions.

But I also use the concept of a Slice which is allows for the division of the database into time periods like 'today', 'yesterday', 'last week', 'May 2005'. Each feed may actually have many different expressions for the same concept in different slices overlapping. But feed_expressions acts like an Array which gives me a problem. I'm going to get the expressions from every slice.

In that case I'm going to have to manually filter out the expressions from just the slice I am interested in, e.g.:

slice = Slice.find_by_name( 'this_week' )
feed.feed_expressions.select { |e| e.slice == slice }

That looks pretty ugly and things are only going to get worse when we realise how much overhead filtering all those unwanted slices is going to create on every access.

However Recipe 24 to the rescue... It turns out that feed_expressions is not a real Array but an AssociationProxy masquerading as an Array. And Rails lets you add functionality to those proxies, so:

module BySlices
    def for_slice( slice )
        find( :all, :conditions => ['slice_id = ?', slice.id] )
    end
end

and a slight modification to Feed along the lines of:

class Feed < ActiveRecord::Base
    has_many :feed_expressions, :extend => BySlices
end

means that I can now use:

feed.feed_expressions.for_slice( Slice.find_by_name( 'this_week' ) )

to get exactly the expressions I want and the filtering happens in SQL before ActiveRecord has to instantiate anything. Neat!

11/05/2006 20:15 by Matt Mower | Permalink | comments:
More about: