Reputation: 41551
Our app uses Rails.cache
in the controller to cache some items outside the scope of the view (like meta tags), then uses fragment_caching on the bulk of the view.
The view caches one main model, but we have used data from 5 other models (not connected by an Association) inside that main cache. It's easy to expire the fragment with a sweeper on the main model, but those additional models also change and need to trigger this page to expire.
We can't use a regexp route for deleting the cache keys because we have to reference this cache entry by only the main model -- the other models are determined by an expensive query that we perform inside the cache block in the controller.
Does Rails 3 have a way to essentially use tags to mark a cache entry, so we can trash it when any of the 6 models on the page change, but we can still find the cache entry from only the main model's key?
Here's some dummy code to express the idea:
In the controller
@cache_key = "/page/#{params[:name]}/#{params[:id]}"
unless fragment_exist? ( { :slug => @cache_key })
# run our processes here that will be needed in the view,
# then cache the data that is used outside the view
Rails.cache.write(@cache_key, { (data goes here) } )
# run our expensive query here:
@similar_pages = Page.pricey_query!.limit(5).all
else
cached = Rails.cache.read(@cache_key)
end
In the view
- cache( {:slug => @cache_key} ) do
- @similar_pages.each do |page|
= image_tag page.photos.first.image.url
-# more pretty stuff here
My goal:
Okay, easy!
Umm... #(*$^*@ .. does ... not ... compute.
Upvotes: 3
Views: 1160
Reputation: 311
Cashier may be helpful. "Tag based caching"
# in your view
cache @some_record, :tag => 'some-component'
# later
Cashier.expire 'some-component'
https://github.com/twinturbo/cashier
Upvotes: 0
Reputation: 41551
Just as tadman states in the comments of the question, I had to invent my own solution, since Rails doesn't technically allow tags in the sense that I needed them. Here's a generalized solution for those interested in doing something similar:
I created a new table called SimilarPages
:
create_table :similar_pages, {:id => false} do |t|
t.integer :page_id, :similar_page_id
# you could also do `t.string :tag_name` or similar
end
add_index :similar_pages, :page_id
add_index :similar_pages, :similar_page_id
Technically, I could do a self-referential has_many
relationship on Pages
, but I decided not to since I don't ever need to reference it that way. I just created a simple SimilarPage
model:
class SimilarPage < ActiveRecord::Base
belongs_to :page
belongs_to :similar_page, :class_name => 'Page'
end
Then using ar-extensions
(because I'm lazy, and also because I wanted to do this in one INSERT statement), I do this within the cache block:
SimilarPage.delete_all("page_id = '#{@page_id}'")
SimilarPage.import [:page_id, :similar_page_id], @similar_pages.collect {|s| SimilarPage.new(:page_id=>@page_id,:similar_page_id=>s.id)}
In my expire_cache_for
method of my Observer, I do this:
SimilarPage.where(:similar_page_id => expiring_page.id).all.each do |s|
ActionController::Base.new.expire_fragment(/page_show__#{s.page_id}__.*/)
# the regexp is for different currencies being cached ^
Rails.cache.delete("page_show_#{s.page_id}")
end
Upvotes: 2
Reputation: 4937
There is an excellent explanation of how Rails generates tags for content expiration in this video http://railslab.newrelic.com/2009/02/19/episode-8-memcached
The short if it is if you pass your entire object into the cache it will generate a cache key that that uses the timestamp which will get updated when you change your object.
Upvotes: 0