#89
Jan 20, 2008

Page Caching

Page caching is an efficient way to cache stateless content. In this episode I will show you how to cache the dynamic javascript we created last week.
Download (21.9 MB, 6:31)
alternative download for iPod & Apple TV (11.7 MB, 6:31)

Resources

# javascripts_controller.rb
caches_page :dynamic_states

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

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

# app/sweepers/state_sweeper.rb
class StateSweeper < ActionController::Caching::Sweeper
  observe State
  
  def after_save(state)
    expire_cache(state)
  end
  
  def after_destroy(state)
    expire_cache(state)
  end
  
  def expire_cache(state)
    expire_page :controller => 'javascripts', :action => 'dynamic_states', :format => 'js'
  end
end

# states_controller.rb
cache_sweeper :state_sweeper, :only => [:create, :update, :destroy]

RSS Feed for Episode Comments -44 comments

1. pnowak Jan 20, 2008 at 23:50

pretty nifty piece of code, thanks.


2. AdulteratedJedi Jan 21, 2008 at 01:19

maybe you should release a TM Bundle with all your nifty snippets in :-)


3. AJ Jan 21, 2008 at 01:42

This episode is not showing in the "Full" podcast, its at episode 88. However, the iPod feed is current.


4. Zigzo Links Jan 21, 2008 at 10:53

Ryan, this is something i have been very interested in lately. thanks.


5. Mike Cleaner Jan 22, 2008 at 12:25

Ryan, your 'casts always get me revved up for a long day of refactoring. You don't talk down to your audience and you don't have a huge rub-ego, and that keeps me coming back for more. I even bought a couple Peepcodes! Well done sir, and keep them coming.


6. Peter D Jan 23, 2008 at 05:20

This may be a really dumb question but since the sweeper observes the State model, why is it necessary to also declare the cache_sweeper in the controller?


7. Garrett Heaver Jan 24, 2008 at 23:50

Peter D, I was wondering the same thing? Are we missing something?


8. Bounga Jan 25, 2008 at 01:09

Peter D and Garrett Heaver:

I don't necesseraly wants to do the sweeping job in every controller. It's a matter of features and needs.

So if you want to enable the sweeping jog, you have to tell it in the given controller using cache_sweeper.


9. Ben Jan 26, 2008 at 23:52

Please post the sweeper snippet!


10. Ryan Bates Jan 27, 2008 at 22:30

@Peter D, that's a really good question. I'm not entirely sure why a sweeper behaves differently than an observer. It even has the observe line. My guess is it has something to do with the scope in which it is called. It needs access to the request object which is available to the controller.

@Ben, you can find the snippet here: http://pastie.caboo.se/144239

Maybe someday I'll release a TM bundle with all my snippets, but right now they aren't organized well enough.


11. Philip (flip) Kromer Jan 29, 2008 at 16:18

This is, as usual, quite awesome.

I don't like having the cached files appear in public/ -- it makes life hard for doing svn (a whole bunch of spurious cached files appear there). I also don't like the idea of setting public/ world writeable (or even having world-writeable files in the cache) on the production end.

After some effort, I got all this working under nginx and rails 2.0; see instructions here if you're interested:
  http://pastie.caboo.se/145116


12. Marcus Derencius Feb 11, 2008 at 13:37

This screencast and the before are great.

I just want to share a tip for a small caching challenge that I had.

usually I set the cache directory to "public/cache", but this "break" the caching of dynamic javascripts.

A solution that I found is to redefine @page_cache_directory on javascripts_controller

Check it here: http://pastie.caboo.se/150570
 
Hope it may be useful.

Marcus


13. francisoud Mar 03, 2008 at 08:22

I have a problem when trying to use this tutorial :(

I get this error message :
uninitialized constant StateSweeper::State

RAILS_ROOT: C:/developement/ruby/minotaur
c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/dependencies.rb:478:in `const_missing'
app/sweepers/state_sweeper.rb:3
-e:2:in `load'
-e:2

I double checked and I don't see what I forgot :(

Sweeping doc is not really helpful either:
http://rails.rubyonrails.com/classes/ActionController/Caching/Sweeping.html

any idea ?


14. francisoud Mar 03, 2008 at 08:33

Ok never mind I got it.

We need to have an object "State" in models/state.rb and I don't have such an object in my project.

The pastie for textmate helped me to understand http://pastie.caboo.se/144239 ;)


15. partydrone Apr 11, 2008 at 08:39

How would I do this for a RESTful resource? Specifically, would I just put the path in the expire_page command?

def expire_cache(article)
  expire_page article_path(article)
end


16. partydrone Apr 11, 2008 at 08:44

Another question:

You don't specifically add the "caches_page" command to the states controller, just the "cache_sweeper" command. Is that in place of the "caches_page" command?


17. eddie May 06, 2008 at 07:18

Is it possible to observe more than one model in a sweeper and have different events carried out depending on which model changes?

For example:

class StateSweeper < ActionController::Caching::Sweeper
  observe State, Foo

  def after_save(state)
    expire_cache(state)
  end
 
  def before_save(foo)
    if foo.someparameter > 1
      expire_cache(state)
    end
  end

  def expire_cache(state)
    expire_page :controller => 'javascripts', :action => 'dynamic_states', :format => 'js'
  end
end

I've tried doing something like the above but i doesn't work - I can't figure out how to differentiate between which model changes, a State model or Foo model - the before_save and after_save functions are triggered whether the model being changed is a State or a Foo ...
I could probable use instanceof? inside each action, but this seems a little messy.


18. Webdesigner May 16, 2008 at 02:11

Good job! Thanks for the screencast. Has definitely saved me a few hours of work...


19. Matt Jul 30, 2008 at 10:13

@eddie, I would say that it is possible, the parameter passed to the active record observer methods (after_save and after_destroy) is the record which can be tested to check what it is, check out the api documentation, it has an example using two models: http://api.rubyonrails.com/classes/ActionController/Caching/Sweeping.html


20. Jonathan Spooner Sep 10, 2008 at 22:29

How do you suggest removing cache for a large number of items and multiple conditions. For example domain.com/:controlller/:action/:id/:season/:collection. This url could leave hundreds of cached files.

I see the docs suggest looping through each item but in my situation this is not reasonable.

list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }

Thanks for the great screencast!!!!


21. blakrishnan Jun 11, 2009 at 01:34

My application has blog section, where the updates will be happen often (lets assume 5 min once).people always comment for the blog. so if i delete the cached page every time while the comment has entered, what is the point of doing caching in this case. is there any other technique available to overcome this?


22. Leather Blazers Nov 08, 2009 at 01:43

Hi..
It is a good and informative post..
The way of writing is very nice


23. tv pc Nov 08, 2009 at 01:48

Hi..

thanks for sharing such a nice post...It is really a nice idea...


24. Wholesale watches Nov 23, 2009 at 18:41

Good page!


25. Increase your vertical Dec 17, 2009 at 01:12

Thanks for the useful code :) Its well hacked together for what I'm looking for :D

I might port it to python later :P


26. How To Increase Vertical Jan 19, 2010 at 11:31

Good stuff!

Thanks mate :-)


27. uggs on sale-ugg outlet store Feb 03, 2010 at 01:14

 Thank u for sharing,very good and useful for me.I will have eyes on your post,and i am Looking forward to more useful info!


28. us drugstore Feb 10, 2010 at 03:36

Thanks for the review. I also don't like the idea of setting public/ world writeable (or even having world-writeable files in the cache) on the production end.


29. animal shaped rubber bands Jul 26, 2010 at 21:38

[url=http://www.toptoys2trade.com/power-balance-wholesale-2-c-40/ ]power balance[/url],


30. Exclusive Sneakers Aug 01, 2010 at 23:32

Thanks for the great screencast!!!!


31. p90 workout Aug 12, 2010 at 09:18

I found your blog on Yahoo and I just wanted to say that I think your writing is simply stunning! Thanks again for providing this content for free.


32. cheap air jordans Aug 16, 2010 at 00:23

Good stuff! We need to have an object "State" in models/state.rb and I don't have such an object in my project.


33. christian louboutin simple pump Aug 25, 2010 at 00:38

That is a idea, I am agree with the opinions of this articles about


34. louis vuitton shoes Aug 26, 2010 at 20:56

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


35. discounted designer handbags Aug 28, 2010 at 20:17

My friendS told me that this blog is competitive. i will continue to read.


36. retro jordans Aug 28, 2010 at 20:22

I have never read such a wonderful article and I am coming back tomorrow to continue reading.


37. discount jordan shoes Aug 28, 2010 at 20:23

The article is worth reading, I like it very much. I will keep your new articles.


38. discount clothes Aug 28, 2010 at 20:24

These information helps me consider some useful things, keep up the good work.


39. cheap clothes online Aug 28, 2010 at 20:25

This article is informative and interesting,I enjoy reading it.


40. cheap designer clothing Aug 28, 2010 at 20:26

It is my pleasure to read this page,I look forward to reading more.


41. cheap designer clothes Aug 28, 2010 at 20:27

 I really like this website , and hope you will write more ,thanks a lot for your information.


42. snow boots Aug 31, 2010 at 00:38

So if you want to enable the sweeping jog, you have to tell it in the given controller using cache_sweeper.


43. 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