#93
Feb 17, 2008

Action Caching

Action caching behaves much like page caching except it processes the controller filters. You can also make it conditional as seen in this episode.
Download (19.8 MB, 7:04)
alternative download for iPod & Apple TV (11.2 MB, 7:04)

Resources

# products_controller.rb
cache_sweeper :product_sweeper, :only => [:create, :update, :destroy]
caches_action :index, :cache_path => :index_cache_path.to_proc

#...

private

def index_cache_path
  if admin?
    '/admin/products'
  else
    '/public/products'
  end
end

# config/environments/development.rb
config.action_controller.perform_caching = true

# config/environment.rb
config.load_paths << "#{RAILS_ROOT}/app/sweepers"

# app/sweepers/product_sweeper.rb
class ProductSweeper < ActionController::Caching::Sweeper
  observe Product
  
  def after_save(product)
    expire_cache(product)
  end
  
  def after_destroy(product)
    expire_cache(product)
  end
  
  def expire_cache(product)
    expire_action '/admin/products'
    expire_action '/public/products'
  end
end

RSS Feed for Episode Comments 13 comments

1. Brad Gessler Feb 17, 2008 at 23:44

Got a link to that caching plugin you were talking about?


2. Ted Feb 18, 2008 at 07:11

Hey Ryan, I'm wondering if it could have been a little more clever and used string replacement like this:

caches_action :index, :cache_path => Proc.new{ "/products/#{admin?}" }

That would put the cache files as "/products/true" and "/products/false". Granted not the best of names, but what do we care what the cache files are named under the hood? ;-)


3. Andrew Kaspick Feb 18, 2008 at 08:41

I have a plugin called conditional caching that I wrote years ago, and is still maintained.

http://agilewebdevelopment.com/plugins/conditional_caching

Although after seeing this railscast, there are some features (:cache_path) that didn't exist when I first wrote it, so I think I'll be making another update of the plugin soon to make use of it. :)


4. Mitchell Blankenship Feb 19, 2008 at 13:31

First off Ryan thank you so much for all your railscasts. They have been invaluable in my learning process. Now I'm wondering if action caching will work with pagination. I tried a couple of different things but I can't seem to get it to work. I was just wondering before I go to a lot of recoding on my existing blog to add this.


5. Andrew Kaspick Feb 19, 2008 at 17:40

I notice that :cache_path removes the name of the host if a string is returned.

Without :cache_path the fragment may be called "localhost:3000/foobar", but if you specify :cache_path => "/foobar", the fragment is written as just "/foobar" without the host which is a big difference.

Using the to_proc technique explained here returning a string is basically the same as using just a string, so you'll lose the hostname in the fragement as well.

So if you depend on the host in the written fragment, you'll have to include the hostname in your string.


6. Ryan Bates Feb 24, 2008 at 22:43

Sorry for the delayed response everyone.

@Brad, the plugin I was referring to is called Action Cache. However I'm not entirely sure if it works with Rails 2.0, there are alternative solutions as Andrew mentioned.
http://wiki.rubyonrails.org/rails/pages/Action+Cache+Update+Plugin

@Ted, yep, that should work. I prefer the more descriptive names but to each his own. :)

@Mitchell, in order to get this to work with pagination you'll need to include the page number in the name of the cache. You can use the dynamic technique as shown at the end of this episode to put the params[:page] value in the name of the cache and it should work.

@Andrew, you're right, the domain name gets chopped when passing a string as the path. This behaves the same in fragment caching as well. I can see how either behavior could be desirable. If you do want the domain with the port in the path then you can include that in the string.


7. Wallen Feb 28, 2008 at 06:53

Excellent tutorial! I did not feel cheated ;).

Why bother caching the admin page? Is it not possible to cache_action :if ?


8. Ryan Bates Feb 28, 2008 at 08:52

@Wallen, as far as I know there's no "if" option. So if you want to disable caching entirely you should look into a plugin as mentioned above. Actually, I've never tried returning nil for the cache path so that might work too.


9. Mislav Mar 16, 2008 at 09:17

Great episode! I liked the twist :)


10. Dave Mar 21, 2008 at 11:33

Ryan, thanks for the great screencasts. Does the :cache_path work for Rails 1.2.6? My proc never seems to get called.


11. Amos King Apr 04, 2008 at 15:20

I am caching a lot of actions and some with ids. I was caching them under different folders. My get path uses this string to return:

"/#{params[:controller]/params[:action]"

There is another version with the id tacked on. But expire_action is not removing any of the caches. My call in foo_sweeper looks like this.

expire_action "/foo"

any ideas?

Thanks


12. Zaphod Beeblebrox Oct 14, 2008 at 09:50

I really do not understand. I'm in the case of Mitchell Blankenship. Ok to named paginated pages path dynamically but how to destroy them with the sweeper ? The great way expire_fragment (% r (pages / \ d * /) notes) does not work with expire_action! It's unbelievable! I found this example that seems to work but it is inconceivable! http://www.fngtps.com/2006/01/lazy-sweeping-the-rails-page-cache You have a better method ?


13. jc Nov 19, 2008 at 08:11

Those looking for the pagination code, I used this and it seems to work (using restful authentication for the login info):

def index_cache_path
if logged_in? && current_user.has_role?('administrator')
"/admin/questions/#{params[:page]}"
else
"/public/questions/#{params[:page]}"
end
end


14. jc Nov 20, 2008 at 14:29

Hey Ryan -

I figured out how to get paginated pages to cache, but I'm having trouble getting them to expire:

def expire_cache(question)
    expire_action "public/questions/"
  end

The index page (page 1) expires properly, but all other pages do not - they cache once and never expire.

I've tried putting * at the end of that path and also including the #{params[:page} at the end of the path (I don't think this should work though since that isn't passed). How can you erase the cache for an entire directory?


15. jc Nov 20, 2008 at 15:28

==How to expire paginated pages

So after a lot of research I've figured out how to do this somewhat properly. Short answer is:
def expire_cache(question)
    expire_fragment(%r{public/questions.*})
  end

Here's the explanation:

1. You can't use expire_action, you need to use expire_fragment
2. You need to use a regex

So if you take a look at the source for expire_action and expire_fragment you'll notice that expire_fragment has a "key" passed to it and expire_action does not accept one:

http://api.rubyonrails.org/classes/ActionController/Caching/Fragments.html
http://api.rubyonrails.org/classes/ActionController/Caching/Actions.html

You'll also notice that expire_action is largely just a proxy to expire_fragment if all you're doing is passing it an actual path.

You'll probably want to craft your regex much more elegantly and safely than I have done here, especially if you are caching more than just one thing.

In the end though I see it as an issue with the rails code and expire_action should take the regex as an argument just like expire_fragment does I think.


16. Zaphod Beeblebrox Nov 24, 2008 at 01:55

@jc Thank you for this very interesting explanation. This may be a good alternative to the FileUtils.


17. P.J. Jan 26, 2009 at 13:17

For pagination I use:

def index_cache_path
  p = ''
  p = params[:page] if params[:page] and params[:page] != 1
  if logged_in? and current_user.has_role?('super user')
    '/admin/people' + p
  elsif logged_in?
    '/private/people' + p
  else
    '/public/people' + p
  end
end

My problem is that some of my cached index actions respond to an XML format for RSS feeds, but they are unreachable because the action is cached. Is there a way in my index_cache_path to check if a request was XML so I can append 'xml' to the returned cache path?


18. Jeff Paul Scam Mar 04, 2009 at 11:30

This post gave us a major Brainstorm session of all the possibilities we can utilize on our blog.


19. Bladeless electric fan Oct 18, 2009 at 03:26

I don't really catch the full idea. Can you do a more detailed guide?


20. Jamie Hill Dec 10, 2009 at 06:55

Ryan, you are a star. I've been fiddling about with the :layout => false option for a while trying to achieve this however it fails when using content_for, this solution is ideal... Thanks!!


21. winttery Jan 06, 2010 at 22:54

i am looking for this.thanks to this opportunity,i utimately find this. very appreciated for the download source.


22. Compro Oro Mar 06, 2010 at 07:40

Caching is what Google makes when visit a website. nice work!


23. timberlandbootsuk Aug 02, 2010 at 02:08

we provide our buyers with an efficient and manageable procurement process covering every phase of the international supply chain and

streamlining trade channels. Also welcome wholesaling, feedback now!


24. free directory list Aug 11, 2010 at 22:25

It is amazing! Thanks!


25. 90x workout Aug 12, 2010 at 09:27

I am not much into reading, but somehow I got to read nice information on your site. Simple to understand and helpful. We will look forward for your future updates.Thanks!


26. Air Jordan AJF 3 Aug 17, 2010 at 19:54

nice work!I am looking for this.thanks to this opportunity,i utimately find this. very appreciated for the download source.
Thanks!


27. christian louboutin flats Aug 25, 2010 at 00:39

it's a good application. Thanks


28. louis vuitton shoes Aug 26, 2010 at 21:02

Thanks for sharing your article. I really enjoyed it. I put a link to my site to here so other people can read it. My readers have about the same interets


29. snow boots Aug 31, 2010 at 00:35

They have been invaluable in my learning process. Now I'm wondering if action caching will work with pagination. I tried a couple of different things but I can't seem to get it to work.


30. batteries Sep 02, 2010 at 07:58

So far I have tried using a Privileges table that holds a user_id, role_id and account_id, but I can't find a way to find out if the current user within the current account belongs to a certain role.

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player
Give Back to Open Source