# Association Field Find Caching Technique

**URL:** <https://www.ruby-forum.com/t/association-field-find-caching-technique/135385>\
**Category:** Rails\
**Created:** [April 21, 2008, 7:14pm UTC](https://www.ruby-forum.com/t/association-field-find-caching-technique/135385 "2008-04-21T19:14:46Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![sjmadsen](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/b3f665/32.png) [@sjmadsen](https://www.ruby-forum.com/u/sjmadsen)\
**Post date:** [April 21, 2008, 7:14pm UTC](https://www.ruby-forum.com/t/association-field-find-caching-technique/135385/1 "2008-04-21T19:14:46Z")

</div>

Hi everyone,

I tend to be searching my association collections a lot for specific  
field values. I could do a foo.bars.find\_all\_by\_fieldname(fieldvalue)  
each time, but why hit the database over again? Especially when the  
full collection is already in memory. I wrote this module to extend  
associations to look at the already loaded collection for field/value  
matches.

module MyAssociationExtentions

def field\_find(field, value, opts = {})  
@field\_cache = nil if opts[‘reload’]  
((@field\_cache ||= {field =\> {}})[field] ||= {})[value] ||=  
self.is\_a?(Enumerable) ? (self.select { |task| task.send(field) == value  
}) : (self if self.send(field) == value)  
end

end

Here it is again written out into multi lines so it is easier to read in  
the forum

module MyAssociationExtentions  
def field\_find(field, value, opts = {})  
@field\_cache = nil if opts[‘reload’]  
@field\_cache = {} unless @field\_cache  
@field\_cache[field] = {value =\> []} unless @field\_cache[field]  
@field\_cache[field][value] ||= self.is\_a?(Enumerable) ? (self.select  
{ |task| task.send(field) == value }) : (self if self.send(field) ==  
value)  
end  
end

Questions:

1. I use a multi-dimentional hash to store each potential field/value  
lookup. Is this too memory intensive?
2. Does this even theoretically improve performance vs the database? or  
is it a waste of time
3. Is there a better way to write that line (all those annoying checks  
to see if the hash is already there)
4. could I push this into memcache to lower the memory usage by  
distributing it across mongrels.

Thanks

---

<div class="post-metadata">

**Author:** ![sjmadsen](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/b3f665/32.png) [@sjmadsen](https://www.ruby-forum.com/u/sjmadsen)\
**Post date:** [April 22, 2008, 3:02am UTC](https://www.ruby-forum.com/t/association-field-find-caching-technique/135385/2 "2008-04-22T03:02:52Z")

</div>

Steve,

Have you tried to benchmark your solution, this should answer the  
question whether this solution has any performance gains. Generally  
speaking anything stored in physical memory is accessible much much  
faster than any IO operations.

Also in your solution, when do you invalidate the @field\_cache and re-  
read the field values from database ?

Regards,  
-daya

On Apr 21, 12:14 pm, Steve M. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)

---

<div class="post-metadata">

**Author:** ![sjmadsen](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/s/b3f665/32.png) [@sjmadsen](https://www.ruby-forum.com/u/sjmadsen)\
**Post date:** [April 23, 2008, 4:47pm UTC](https://www.ruby-forum.com/t/association-field-find-caching-technique/135385/3 "2008-04-23T16:47:13Z")

</div>

Hi Daya,

Don’t know how I missed out on require ‘benchmark’, but I did some  
testing with it, and it is so much faster for finding by field.

The first time it runs it performs about the same as doing a find\_by  
because it hasn’t loaded the collection, if the collection is already in  
memory it is lightning fast. I have added a reload flag that will skip  
the use of the @field\_cache in case dynamic data is being used.

Once the collection loaded finders should not hit the DB anymore, they  
are too expensive, Let me know if you see any holes in this. -Steve

Here are some benchmarks

setup  
foo = Foo.find(:first)

Hitting the DB on each look  
Benchmark.bm { |x| x.report { 5000.times {  
foo.bars.find\_all\_by\_some\_field(1) } } }  
user system total real  
14.260000 3.030000 17.290000 ( 18.280076)

Without Field Caching

Benchmark.bm { |x| x.report { 5000.times {  
foo.bars.field\_finder(“some\_field”, 1, true) } } }  
user system total real  
0.210000 0.070000 0.280000 ( 0.269146)

With Field Caching

Benchmark.bm { |x| x.report { 5000.times {  
foo.bars.field\_finder(“some\_field”, 1, false) } } }  
user system total real  
0.110000 0.040000 0.150000 ( 0.155943)
