The author, smiling winningly Scott Raymond home

Caching images in Rails

06 Jul 2005

Blinksale, not surprisingly, allows you to upload your company’s logo, so that it can be included in invoices. Generally, the best practice is to store uploaded files in the filesystem, rather than database BLOB fields, because of the overhead in repeatedly retrieving large amounts of data from the database. In the case of Blinksale, though, we decided to store logos in BLOBs, so that we would have a single target for backups and replication. The logos are fairly small files, but serving them directly from the database still created a performance bottleneck.

Rails takes a delightfully lo-fi approach to caching pages — it writes the output of an action to a file in the public directory, so that next time the URL is requested, the web server delivers the file statically; Rails is never even invoked. You can turn on this page-level caching with just one line, but it’s not immediately obvious how to use it for binary files. Turns out it’s not hard, and it’s perfect for logos — MySQL keeps the master copy, but lighttpd will serve them lightning-fast.

In the controller, create an action to serve the image from the database, and turn on page caching to save the output. Here’s what Blinksale’s AccountController looks like:


   caches_page :logo
   def logo
     send_data Firm.find(@params['id']).logo,
      :filename => 'logo.gif', :type => 'image/gif',
      :disposition => 'inline'
   end
   def upload_logo
      # ...
      expire_page :controller => 'account',
         :action => 'logo',
         :id => "#{@firm.id}.gif"
   end

In routes.rb, we have this line:


   map.connect 'account/logo/:id',
      :controller => 'account',
      :action => 'logo', :id => /^.*\.gif$/

That last bit is where the magic happens — it uses a regular expression to ignore a “.gif” file extension, which allows our logo URLs to live a double life as both a static file and a Rails action call.

Now, the first time a a browser requests “/account/logo/1234.gif”, it will be handled by AccountController#logo. Page caching will then create a file at public/account/logo/1234.gif, and subsequent requests will be served statically. When a new logo is uploaded, the cached version will be deleted, and the next request will regenerate it.