The original "roll your own" autocomplete action:
1 2 3 4 5 6 7 8 9 10 11 |
def auto_complete_for_email_destination value = params[:email][:destination] @contacts = @user.contacts.find(:all, :conditions => [ 'LOWER(email) LIKE ? OR LOWER(name) LIKE ?', '%' + value.downcase + '%', '%' + value.downcase + '%'], :order => 'contacts.email ASC', :limit => 5) render :partial => 'contacts/destination_autocomplete' end |
now, we change it to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
def auto_complete_for_email_destination value = params[:email][:to_all] @contacts = @user.find_user_contacts(value,{:limit => 5}) render :partial => 'contacts/destination_autocomplete' end # User model require 'digest/sha1' class User < ActiveRecord::Base acts_as_cached after_save :reset_cache # find contacts with matching pattern # the key to the cache is the user.id def find_user_contacts(query,options = {}) user_id = self.id limit = (options[:limit] ||= 5).to_i contacts = User.get_cache(self.id).cached(:contacts) # iterate through the contacts and use regexp to match it reg = Regexp.new(query) # find_all works with Enumerator Class. results = contacts.to_enum.find_all {|t| (t.email =~ reg) || (t.name =~ reg)} results[0..(limit-1)] end end # Contact mode, sweep the user cache if saved. class Contact < ActiveRecord::Base belongs_to :user acts_as_cached after_save :reset_cache after_save :sweep_cache_from_user def sweep_cache_from_user User.reset_cache(self.user_id) end |
if you use shared host, you could as well use Ferret instead of memcached.
That's it folks...
Speed improvement:
before: ~ 20-40 req/sec
now: ~ 350-400 req/sec
on Amazon EC2 small AMI
Note: Experimental code... test it well before using in production...