DRY association sweeping with acts_as_cached
Cherry picking exactly which models and associations to cache with Acts as Cached is dead simple.However, cached object associations quickly result in repetitive sweeping snippets.
How to cache associations
class Address < ActiveRecord::Base
acts_as_cached :version => 1, :include => [:country]
belongs_to :addressable, :polymorphic => true #we can't do find :include => X with polymorhic associations
belongs_to :country, :class_name => 'Globalize::Country', :foreign_key => 'country_id'
end
The above snippet will execute the following behind the scenes:
Address.get_cache(1) #Address.find(1, :include => [ :country ])
How to sweep associations
Overriding accessors
class Address < ActiveRecord::Base
def country
Globalize::Country.get_cache( country_id ) unless country_id.nil?
end
end
Just works, always fresh but has the overhead of a ridiculous amount of CACHE.get lookups
Manually sweeping
class Address < ActiveRecord::Base
after_save :cache_sweeper
after_destroy :cache_sweeper
def cache_sweeper
expire_cache
country.expire_cache
end
end
and
class Globalize::Country < ActiveRecord::Base
has_many :addresses, :as => :addressable
after_save :cache_sweeper
after_destroy :cache_sweeper
def cache_sweeper
expire_cache
addresses.each{|a| a.expire_cache }
end
end
Very bloated for complex relationships.
But there's a better way
Add the following to actsas_cached's InstanceMethods or Monkey Patch/moduleeval in your environment.
def expire_cache_with_associations( *associations_to_sweep )
((self.class.cache_options[:include] || []) + associations_to_sweep).flatten.uniq.compact.each do |assoc|
macro = self.class.reflect_on_association(assoc.to_sym).macro
macro == :has_many ? self.send(assoc.to_sym).each{|r| r.expire_cache unless r.nil? } : self.send(assoc.to_sym).expire_cache unless self.send(assoc.to_sym).nil?
end
expire_cache
end
This snippet will sweep associations passed in the :includes config hash option, but also allows for additional associations to be merged in later on.
A DRY and refactored example
class Address < ActiveRecord::Base
acts_as_cached :version => 2, :include => [:country]
#sweeps country
after_save :expire_cache_with_associations
after_destroy :expire_cache_with_associations
end
and
class Globalize::Country < ActiveRecord::Base
acts_as_cached :version => 1
has_many :addresses, :as => :addressable
after_save :cache_sweeper
after_destroy :cache_sweeper
#We don't cache all addresses with the country as one country can grow to a potentially
#large number of adresses ... we just sweep to update the cached country for each address
def cache_sweeper; expire_cache_with_associations(:addresses) end
end
Sweeping polymorphic associations
class Address < ActiveRecord::Base
acts_as_cached :version => 1, :include => [:country]
belongs_to :addressable, :polymorphic => true
belongs_to :country, :class_name => 'Globalize::Country', :foreign_key => 'country_id'
after_save :cache_sweeper
before_destroy :cache_sweeper
protected
#sweep the related addressable entitiy
def cache_sweeper; expire_cache_with_associations(:addressable) end
end
class Customer < ActiveRecord::Base
acts_as_cached :version => 1, :include => [:address, :contact]
has_one :address, :as => :addressable, :dependent => :destroy
has_one :contact, :as => :contactable, :dependent => :destroy
#nothing fancy here
after_save :expire_cache_with_associations
before_destroy :expire_cache_with_associations
end
class Contact < ActiveRecord::Base
acts_as_cached :version => 1
belongs_to :contactable, :polymorphic => true
after_save :cache_sweeper
before_destroy :cache_sweeper
protected
#sweep related contactable entitiy
def cache_sweeper; expire_cache_with_associations(:contactable) end
end
1 comment
Jump to comment form | comments rss [?]