Migrating From FileColumn To Paperclip

Over the first few years of Rails' history, there have been a few attempts to simplify the process of handling file uploads. FileColumn was one of the early winners. Now Paperclip is becoming popular, for a variety of reasons (among them the lack of a dependency on RMagick, which is the source of enough problems to fill a few more blog posts.)

Recently, I decided to take one of my older Rails projects and replace FileColumn with Paperclip. The process isn't exactly straightforward, but it can be done.

Let's start by looking at how FileColumn and Paperclip keep track of uploads. For this post, we'll work with an application that shows products in a catalog. Each product has an image to be shown alongside its specs in the catalog.

FileColumn uses a single column in the products table, called (for this application) image. This column contains the file name of the image, as uploaded by the user.

Paperclip uses four columns in the products table. These are:

  • image_file_name is the file name of the image - just like the image column in FileColumn.
  • image_content_type is the MIME content type of the image, such as image/jpeg.
  • image_updated_at is the time that the image was last uploaded.
  • image_file_size is the size of the file on disk (i.e., the size in bytes, not the dimensions in pixels.)

Both FileColumn and Paperclip store their uploads in specially named directories on disk. (Paperclip can also store to Amazon S3, but I haven't tried that.) Both plugins can also specify exactly where these directories are on disk, with one exception: FileColumn will create directories based on the singular name of the class and attribute (i.e. /uploads/product/image) where Paperclip prefers plural names (/uploads/products/images.) Lastly, Paperclip will prepend original_ to the name before storing it on disk, to distinguish it from any thumbnails it may create.

For this post, we'll keep things somewhat simple. The product has one image, which we will assume is a JPEG. We are not concerned with the dimensions of the image, and will not be creating any thumbnails. We simply want to copy the data from FileColumn to Paperclip, preserving all of our uploads. Okay? Let's get started! First, we need to install Paperclip. With reasonably recent versions of Rails, installing Paperclip from GitHub is quick and easy:

 ./script/plugin install git://github.com/thoughtbot/paperclip.git 

With Paperclip installed, we can have it generate a migration to add the columns it needs to the database:

 ./script/generate paperclip Product image 

You can take a look at the migration to see what it's doing, if you like. It'll be called 20081027145020_add_attachments_image_to_product.rb (with a different timestamp.)

Next, let's change how the image is declared in our Product model. FileColumn uses this format:

 file_column :image, :root_path => File.join(RAILS_ROOT, "public/system"), :web_root => '/system/' 

:root_path defines where the images are stored, and :web_root defines the first part of the URL to the image. FileColumn will add /product/image/#{id} to both of these to get the final paths.

Paperclip is a bit more explicit about the pathing, which gives you a bit more flexibility if you want things in different places. We want things in more or less the same place, so we remove the file_column line and add:

 has_attached_file :image, :url => "/system/:class/:attachment/:id/:style_:basename.:extension", :path => ":rails_root/public/system/:class/:attachment/:id/:style_:basename.:extension" 

Next, we need to change our views. Let's start with our form for the :new and :edit actions, where we'd be uploading our image. FileColumn uses a special file_column_field helper to do its thing:

 file_column_field 'product', 'image' 

Paperclip doesn't need it (and won't support it once we remove FileColumn) so change it to a standard file_field:

 file_field 'product', 'image' 

(The application on which I tested all this stuff predates the form_for style of generating forms. You could do something like form.file_field 'image' if your code uses form_for.)

The other views we'll need to change are the views that actually show the image. As with the upload view, FileColumn has a helper to generate the proper URL for your image:

 image_tag url_for_file_column('product', 'image'), :alt => @product.title 

Paperclip makes this, to my eyes, a bit more straightforward:

 image_tag @product.image.url, :alt => @product.title 

(Note, also, that FileColumn's url_for_file_column assumes that the @product instance variable is defined. This might not always be the case. Paperclip makes no such assumption.)

And now, the potentially tricky part: We need to move the data from FileColumn's image field into Paperclip's columns, and rename the files on disk to match Paperclip's expectations. This can be accomplished with a database migration, such as the one I've posted (due to its length) as a gist. There are a few potential optimizations that I'll leave as an exercise to the reader, but something like this should get the job done.

Lastly, we can remove FileColumn. Just remove the entire vendor/plugins/file_column directory.

With all of those changes made and our code checked in, all we need to do is to deploy our application, run the migrations, and we're done - Paperclip takes over for FileColumn, with all of our images and metadata intact. Sweet!

Mark Cornick

,
Posted in Article Category: #Code
on