How do I save Paperclip uploads in a particular folder?

The Paperclip gem is probably the most used gem for uploading files into Rails apps. It’s been around for a very long time, it has a large community around it and is properly battle-tested.

However, it does allow you some control over the generated URLs, but not a great deal.

You can specify the url options in your has_attached_file command. The default puts things into public/system/[class]/[attribute]/[lots of numbers made out of the record ID]/[size]/[filename]. This is a pretty good default, especially if you use Capistrano to deploy your application, as it means that your uploads are stored in the public/system folder, which Capistrano makes sure stays persistent between different deploys of your app. And as it’s in the public folder, it means any web-request received should be picked up by your web-server, it will notice the physical file is there and it will serve it up before your Rails app gets invoked.

But what if you want a bit more control – for example you want the end URL to look something like /users/my-username/images/my-file.jpg ? Paperclip won’t let you do this, because you are relying on a value from a parent object, the user’s username.

However, we can get somewhere quite close; although in the interests of simplicity, the URL will end up being /users/123-my-username/images/456-my-file-jpg

What we do is, instead of Paperclip storing the files in the public/system folder, we store them somewhere else; exactly where depends upon your deployment process, but it needs to be a folder that is persistent across deployments. Of course, you could just use public/system as above and just never publish the Paperclip generated URL – it’s unlikely anyone will ever guess this, so depending upon your security requirements, this may be an OK solution.

Then we add in a Rails controller that is just for serving up our file.

So what we have here is a new route – an ImagesController nested within a Users controller – so the URL to an image will look like this: /users/:user_id/images/:id.

Then we define the “to_param” method on both the User and Image classes. This method is called by Rails to decide what it should put in the URL in cases like this – the default is just the database ID, but we’re going to use the database ID plus some text – the username or the image file-name. Finally we call parameterize on the result, so that it becomes a URL-friendly string.

This means that a User with username “My Username” and ID 123, and an image with file-name “my file.jpg” and ID 456 would end up in a URL of /users/123-my-username/images/456-my-file-jpg

The really clever bit is that when you try to convert “123-my-username” back into an integer, so that you can find the database record, Ruby’s to_i method will convert the “123” and ignore the “-my-username” part. So we are generating URLs that read well to human beings, but we’re also making it easy on Rails when it comes to locating the actual records underneath it all.

Finally, the images controller just loads up the user and requested image, then uses send_file to send the file contents back to the requester.

If we needed an extra layer of security – for example, images are only available to logged in users, or users with a specific permission, we could add in the checks right there in the ImagesController, giving us even more control over who sees what.

Do you know what to do but not how it works?

Ever wanted to understand why Rails views work the way that they do? Why variables from your controllers are visible inside your views?

Sign up below to get a free 5 part email course on how Ruby on Rails view rendering works and gain a deep understanding of the Rails magic.

We will send you the course, plus the occasional update from this web-site. But no spam, we promise, and it's easy to unsubscribe