The key to this technique is making your cache sweepers easy to call from a rake task. For this website I’ve created a simple site sweeper which flushes the entire page cache :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# app/sweepers/site_sweeper.rb

class SiteSweeper < ActionController::Caching::Sweeper
  observe Page, Post, Comment, Tag, Tagging
  
  def after_save(site)
    self.class::sweep
  end
  
  def after_destroy(site)
    self.class::sweep
  end
  
  def self.sweep
    cache_dir = ActionController::Base.page_cache_directory
    unless cache_dir == RAILS_ROOT+"/public"
      FileUtils.rm_r(Dir.glob(cache_dir+"/*")) rescue Errno::ENOENT
      RAILS_DEFAULT_LOGGER.info("Cache directory '#{cache_dir}' fully swept.")
    end
  end
end

By declaring the sweep method as a class method we make it easy to call from rake to force a sweep.

To do so we need to create a new rake file :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# lib/tasks/pixellated.rake

namespace :pixellated do
  namespace :cache do
    desc "Expire the flickr_sidebar fragment cache"
    task :expire_flickr => :environment do
      ActionController::Base.new.expire_fragment('flickr_sidebar')
      RAILS_DEFAULT_LOGGER.info("Flickr sidebar cache swept")
    end

    desc "Expire page cache"
    task :expire_pages => :environment do
      SiteSweeper.sweep
    end

    desc "Expire everything"
    task :expire_all => [:expire_flickr, :expire_pages] do
      RAILS_DEFAULT_LOGGER.info("All caches swept")
    end
  end
end

The tasks in here allow me to flush the page cache as well as flush the flickr_sidebar fragment which you can see to the right of each page. It takes around 8 seconds to talk to flickr and refresh this page element so it’s a perfect candidate for fragment caching.

We can now use rake to flush the caches from the command line :

1
2
3
rake pixellated:cache:expire_flickr
rake pixellated:cache:expire_pages 
rake pixellated:cache:expire_all

But what about on the production server?

Well that’s your local machine sorted, but we need a way to flush the caches on the live server. Capistrano to the rescue again!

Because we can perform cache flushes using rake we just need a way to invoke the rake tasks on the server. Of course we will need to do a deploy to get our newly created rake tasks on to the server first (very important and easy to forget!) :

1
cap deploy

Now we can add the following capistrano tasks to depoy.rb to invoke these rake tasks remotely :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# config/deploy.rb

namespace :pixellated do
  namespace :cache do
    set :remote_rake_cmd, "/usr/local/bin/rake"

    desc "Expire everything"
    task :expire_all do
      run("export RAILS_ENV=production; cd #{deploy_to}/current; #{remote_rake_cmd} pixellated:cache:expire_all")
    end

    desc "Expire page cache"
    task :expire_pages do
      run("export RAILS_ENV=production; cd #{deploy_to}/current; #{remote_rake_cmd} pixellated:cache:expire_pages")
    end

    desc "Expire the flickr_sidebar fragment cache"
    task :expire_flickr do
      run("export RAILS_ENV=production; cd #{deploy_to}/current; #{remote_rake_cmd} pixellated:cache:expire_flickr")
    end
  end
end

Simple. Now if I’ve posted some pictures up to flickr and I want my caches flushed I just run…

1
cap pixellated:cache:expire_all

... and it’s sorted. Plus it uses an existing sweeper used elsewhere in the application, so it’s DRY.

Further reading

Ana Nelson has an entire post on calling rake remotely using capistrano if you are interested in exploring this.

comments powered by Disqus