Sunday, September 4, 2011

Recreate a single version of a CarrierWave::Uploader file

CarrierWave supports a simple way to recreate all the versions of an uploaded file. This comes in handy when you add a new version to your uploader, or when you change the parameters of one of them. For example, assume you have a Post class, with a CarrierWave mounted uploader on the photo column, and you have version called thumb. If you decide to change the resolution of the thumb version, and need to go back and regenerate all existing thumbs, you can do something like this:

Post.all.each do |post|!

What if you have 5 different versions on this Uploader, and you only changed one of them? The above method will download all the versions of every Post#photo from storage (think S3 or Cloud Files), regenerate all of them - even the ones that haven't changed - and re-upload all of them to storage.

That's a lot of wasted time, CPU, and bandwidth.

To address this, I've created a simple monkey patch for CarrierWave to provide a new method called recreate_version!. This method works just like recreate_versions!, except that it takes a parameter specifying a single version to recreate, rather than recreating all of them.

To use this, first drop this monkey-patch in somewhere that it will get loaded. Above your Uploader class will suffice.

Now your script to recompute all the thumbs can look like this:

Post.all.each do |post|!(:thumb)

This will recreate and re-upload only the thumb versions of your Post#photo uploads.

As an added side benefit, the recreate_version! method cleans up after itself. The recreate_versions! method downloads into a temporary cache dir, but leaves it up to the caller to clean that up. This new method assumes that if it had to download and cache the uploaded file and its versions, then it should clean them up immediately.

This method has turned out to save me a lot of time in regenerating large numbers of uploaded file versions. If there's any interest in this, I might work up a pull request with test cases and try to get it into the mainline CarrierWave gem release.