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.

Uploading files for enki using capistrano

Anton Jenkins | February 23, 2009

Because the Enki blogging engine currently doesn’t have a method for uploading files for use in your posts I needed to come up with a quick and easy solution. This solution will also work for any website which doesn’t have an upload facility built in. The technique used is easily replicated.

Could we use git?

It would be tempting to just check the images that are going to be used in posts into git and be done with it. They could be stored in the /public/images directory and get uploaded onto the server when you deploy. But this is messy. Version control is for files that are part of the infrastructure of your site, not the actual content.

So we need a different solution which maintains this separation.

Capistrano

When you deploy with capistrano it creates a symbolic link from current/public/system to the shared/system directory. Because we don’t want to lose our uploaded images whenever we redeploy it makes perfect sense to store our images in the shared area (as this persists across deploys) and have the current deploy link across to them.

But first we need to get them there. What we’re going to do is create the file structure on our local development copy of the site and then use capistrano and rsync to push it up to the server.

To start with we need to create the system directory in public. Oh, and don’t forget to add this folder to your .gitignore! As discussed above, we don’t want this in version control. I’ve opted for the following structure to mimic the structure of the URLs that Enki uses :

As you can see, all the images and files will be in the system/assets directory. You’ll need to ssh into your server and create the assets directory in the corresponding system directory before taking the next step.

We should be at this stage :

Now we need a capistrano task in our deploy.rb to push the files in our local assets directory up to the assets directory on the server :

1
2
3
4
desc "Sync assets on server with development public/system/assets"
task :sync_assets do
  `rsync -qrpt --delete --rsh=ssh public/system/assets deploy_user@myserver.com:/path-to-my-app/shared/system/assets`
end

Note : Don’t forget to alter the above command and change the destination to fit your environment. Unless of course your app does live at deploy_user@myserver.com:/path-to-my-app/!

The combination of rsync and ssh is perfect for uploading the files up to the server. It is important to be aware of the --delete option used. If we delete a file at the source then when we next rsync it will delete the corresponding file at the destination. So make sure you keep all your images on your local machine otherwise the next sync will ditch them on the server!

With everything in place it’s simply a case of firing off the task :

1
cap sync_assets

You won’t get much clue as to whether it worked so ssh into the server and make sure everything has copied over. If it’s not worked then try copying the rsync command and running it from the command line to look at the output. You might have got the paths wrong or something simple like that.

If it’s worked the files can be accessed from your posts using the textile markup like so :

1
!/system/assets/2009/02/23/uploading-files-for-enki-using-capistrano/assets-file-structure.png!

As a quick solution it works quite well!

Join the war on IE6

Anton Jenkins | February 20, 2009

It’s about time!

For too long web designers have had to wrestle IE6 into doing their bidding. The many hours we spend on IE6 bugs are hours we could be investing in better features for our users. Thankfully some developers in Norway have decided to make a stand and spread the word to users that they need to upgrade their IE6 browsers.

By adding something like the following code to your application’s layout file you can add a message only visible to IE6 users asking them to consider upgrading their browsers :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--[if lte IE 6]>
<div style="border:3px solid #ff0000; margin:8px 0; background:#ff8080; color:#000; width:580px;">
  <h4 style="margin:8px; padding:0; font-weight:bold; color:#000">
      Warning : You are using an old version of Internet Explorer
  </h4>
  <p style="margin:8px; padding:0;">Many websites are dropping support for older versions of Internet
    Explorer because they do not conform correctly to industry standards. Not following standards makes
    building websites much harder and wastes time that could be invested in making the website better for
    everyone else.</p>

  <p style="margin:8px; padding:0;">By upgrading your browser you will not only be enhancing your own
    experience but also relieving a significant burden from web designer's shoulders, freeing them to build
    better websites for everyone.</p>

  <p style="margin:8px; padding:0;">There are many modern browsers to choose from...
    <ul>
      <li><a style="color: #000; text-decoration:underline; outline:none" href="http://getfirefox.com">FireFox</a></li>
      <li><a style="color: #000; text-decoration:underline; outline:none" href="http://google.com/chrome">Chrome</a></li>
      <li><a style="color: #000; text-decoration:underline; outline:none" href="http://opera.com">Opera</a></li>
      <li><a style="color: #000; text-decoration:underline; outline:none" href="http://apple.com/safari">Safari</a></li>
    </ul>
  </p>
</div>
<![endif]-->

When viewed with IE6 the user will see the following :

Warning : You are using an old version of Internet Explorer

Many websites are dropping support for older versions of Internet Explorer because they do not conform correctly to industry standards. Not following standards makes building websites much harder and wastes time that could be invested in making the website better for everyone else.

By upgrading your browser you will not only be enhancing your own experience but also relieving a significant burden from web designer’s shoulders, freeing them to build better websites for everyone.

There are many modern browsers to choose from…

Obviously you’ll probably want to play around with the styling to make it fit your site, but the general gist is there for you to hack around with. How you do it is not important, getting involved and helping to spread the word is the main issue.

Hopefully this will gain some momentum around the world and we can move on from the pains of writing IE6 friendly sites.

Using basic authentication to hide your website

Anton Jenkins | February 18, 2009

Suppose you’re developing a rails website for a client and you’d like them to be able to access it on a staging server, but you want to keep it hidden from prying eyes until it’s ready to launch. The quickest and cleanest way is to utilise HTTP basic authentication.

By adding the following code to your application.rb you will protect all pages on the site with a username and password dialog :

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

require 'digest/sha1'

class ApplicationController < ActionController::Base

before_filter :authenticate

protected
  def authenticate
    if Rails.env == "production"
      authenticate_or_request_with_http_basic do |username, password|
        username_hash = Digest::SHA1.hexdigest(username)
        password_hash = Digest::SHA1.hexdigest(password)
        username_hash == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33" && password_hash == "62cdb7020ff920e5aa642c3d4066950dd1f01f4d"
      end
    end
  end
end

What’s with the “0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33”?

Rather than put the username and password in plain text here I’ve obscured them using SHA1 to make it a little more secure. Suppose we want a username of ‘foo’ and and password of ‘bar’ we can use the rails console to obtain the hashes required and paste them in to the above code snippet.

1
2
3
4
5
6
7
8
#./script/console

>> require 'digest/sha1'
=> []
>> Digest::SHA1.hexdigest("foo")
=> "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
>> Digest::SHA1.hexdigest("bar")
=> "62cdb7020ff920e5aa642c3d4066950dd1f01f4d"

Won’t my tests fail?

This is why we specify the rails environment using :

1
if Rails.env == "production"

By doing this we ensure that our tests wont be asked to authenticate and also our development environment will be left alone. If Rails.env doesn’t work for you then try changing this to RAILS_ENV.

Disabling the authentication

Turning it all off is as simple as removing the before filter from your application.rb :

1
before_filter :authenticate  # comment out or remove this line

You may as well leave the authenticate method sitting in your apllication.rb just in case you need to lock things down quickly at a future date.

Other uses for this technique

This method is also very useful for securing the admin areas of your site as detailed in this railscast. If you require something a bit more comprehensive you might want to check out Lockdown, AuthLogic or Restful Authentication.