Your friends at Viget present Extend, a Code & Technology Blog

Paperclip Custom Interpolations

As web developers something we often have to implement is handling file uploads. With Rails there are a number of plugins and gems for helping with this. The one I use most often is Paperclip. Out of the box, Paperclip does exactly what you want, and you don’t have to think about any configuration. But, as your application matures, your needs often change and some custom configuration is often required. One thing you’ll notice is that you can customize where the file is stored. The way that you do this (for those new to Paperclip) is:

has_attached_file :asset, :url => "/system/uploads/:class/:attachment/:id/:basename_:style.:extension"

On a recent project the client wanted us to store uploads in a very specific path structure. On the surface this seemed a simple enough request. As we dug deeper, though, it became clear that one part of the path needed to be based on the attribute of a model that was related to the model on which the file data was being stored. By default Paperclip only allows a limited set of values to be used in the path: filename, timestamp, rails_env, class, basename, extension, id, fingerprint, id_partition, attachment, and style. These predefined interpolations are all you need most of the time, but our case was the exception.

Here’s a basic interpolation definition included in Paperclip:

def basename attachment, style_name
  attachment.original_filename.gsub(/#{File.extname(attachment.original_filename)}$/, "")
end

To create an interpolation you need a method named the same as you will use in your path/URL definition (e.g., basename, style, etc.), the method needs to accept two parameters: the Paperclip attachment object, and the style name (a string). After that the method just needs to return a string to be used as part of the path/URL.

So, let’s say you have a Person class that stores resume uploads and relates the people to a Company:

class Person < ActiveRecord::Base
  belongs_to :company
  has_attached_file :resume
end

If the Company has an attribute named name, you could create a Paperclip interpolation in an initializer file (in `config/initializers`):

Paperclip.interpolates('company_name') do |attachment, style|
  attachment.instance.company.name.parameterize
end

As you can see Paperclip offers a handy convenience method just for this called interpolates that creates a method definition in the necessary class. Now you can add the following code to your Person class:

class Person < ActiveRecord::Base
  belongs_to :company
  has_attached_file :resume, :url => "/system/uploads/:company_name/:attachment/:id/:filename"
end

This now allows us to store resumes by company name. After this, you need to handle the situation where someone changes the name attribute on a company. This involves moving directories and updating all the related records. It's not that hard, so I leave that as an exercise for you to do on your own.

While this certainly isn’t something you need everyday, it’s nice to have this in your bag of tricks. The ease with which you can do this is just another reason Paperclip is such a great library and why I so often find myself coming back to it as my file upload plugin of choice.


Get More From Viget

Subscribe to get our monthly newsletter and occasional special announcements.