Action Association Caching

Hey again guys,

I did come up with a problem few weeks ago and I still don't have an
elegant solution for it. I'd like to hear your opinions on how to deal
with it, since it's rather basic and could be quite common.

Imagine a view showing peoples clothing.
Stefan's hat: stefan.cloths.find_by_location "head"
Stefan's shirt: stefan.cloths.find_by_location "shirt"
Stefan's jeans: stefan.cloths.find_by_location "jeans"
Stefan's left_glove: stefan.cloths.find_by_location "left_glove"
Stefan's right_glove: stefan.cloths.find_by_location "right_glove"
etc...

quite a few queries. bad performance. the thing is though, before
reaching the view the association has been already initialized. so, the
rows already exist in the action, and they are retrieved again
one-by-one in the view.

to avoid retrieving them again, i build a hash in the action this way:
for cloth in cloths; @cached_clothes[cloth.location] = cloth; end
and in the view:
Stefan's hat: @cached_clothes["head"]

It "works". But does not seem elegant. an other approach i tried:
Stefan's hat: stefan.clothes.select{|c| c.location == "head"}.first
Doesnt seem to be the correct "rails way" either.

Is there a "rails way" for doing this?

Thank you :wink:
Stefan

to avoid retrieving them again, i build a hash in the action this way:
for cloth in cloths; @cached_clothes[cloth.location] = cloth; end
and in the view:
Stefan's hat: @cached_clothes["head"]

You can make this slightly neater by saying @cached_clothes =
stefan.clothes.index_by {|c| c.location}

It "works". But does not seem elegant. an other approach i tried:
Stefan's hat: stefan.clothes.select{|c| c.location == "head"}.first
Doesnt seem to be the correct "rails way" either.

Seems reasonable enough. If there were a lot of clothes and clothe
locations the hash approach might be more efficient, but until then it
could be over the top.
If you wanted the syntax to be cleaner I suppose you could fiddle
around with an association extension, eg

has_many :clothes do
   def select_by attribute, value
     detect {|c| c.send(attribute) == value}
   end
end

and then you can do stefan.clothes.select_by :location, 'head'
You could even go the whole hod and leverage method_missing to handle
things like clothes.select_by_location 'x'

If you're only ever doing this by location, you can make things a
little shorter

has_many :clothes do
   def located_on place
     detect {|c| c.location == place}
   end
end

and then you can write stefan.clothes.located_on 'head'

or apply association extensions to the first technique:

has_many :clothes do
   def located_on place
     @cache ||= index_by {|c| c.location}
     @cache[place]
   end
end
(this may or may not be a concern, but in this case you do need to
worry about @cache getting stale, eg as is reloading the association
won't clear it)

Fred

Thanks again fred.

This association extension thing seems like the piece of the puzzle I
was missing.