I have used a number of plugins to attach files to applications with Rails. These include file_upload (remember that) , acts_as_attachment and attachment_fu.
I came across a plugin called Paperclip a few months ago. It looked like the best solution I have seen to simple file attachments since file_upload and it did not rely on the memory monster that is RMagick. I said to myself the next project I work on, paperclip is the way to go.
I have recently had the opportunity to use it on a project I am working on. I have to say that I am very impressed so far, and if any of you have not used it yet, I would advise you to give it a go.
In terms of tutorials, I would highly advise that you start with this tutorial by Jim Neath. I have to say that I was a bit confused in the beginning, but this tutorial helped me on my way.
Image Permissions
As part of the project I needed to direct all requests to the image through an action, to check if the current user that is logged in has permission to see the image. I had a few issues doing this, so I thought I would share how I did that here.
Model:
class User < ActiveRecord::Base
has_attached_file :photo, :styles => { :medium => "300x300>", :thumb => "100x100>" } ,
:url => "/images/show/:id/:style/",
:path => "#{RAILS_ROOT}/attachments/:class/:id/:style/:basename.:extension"
end
I removed all the files from the public directory. That way I was sure that no one can get access to it. That is where the :path => Hash is used. I stored the images in a folder called ‘attachments’ contained within the Rails root folder Here is a list of what the url params are used for:
- class—This is the name of the class the attachment is associated to. In this case User
- id—This is the unique ID of the model the attachment is associated to.
- style—The name of the style. In this case we would have a folder called ‘thumb’ and a folder called ‘medium’ and also a folder called ‘original’
- basename—The file name without the file extension
- extension—Filename extension
The :url hash is directing all image requests to the images controller, making sure to pass all the relevant parameters along with it.
Next I am going to detail the very simple controller :
class ImagesController < ApplicationController
def show
if logged_in? && current_user.allowed_to_view_image?(params[:id])
send_file(current_user.photo.path(params[:style]) , :disposition => 'inline')
else
'#'
end
end
end
As you can see it is very simple. All requests are passed to the show action. The first thing it does is check to see if the user is allowed to see the image using a method called on the User object (I have not detailed it here as it will be different for everybody).
Once permission has been granted simply display an inline send_file request with the same directory structure as defined in the :path url.
Linking
As I am not using Rails 2 for this project, I came across a problem with using image_tag as it saw fit to append a .png to the request. I am not sure how to turn this off and I know it has been ‘fixed’ in Rails 2. My only current solution is to use the old school “img” tag in my views.
Let me know your thoughts
UPDATE
As you can see from Jon Yurek’s (creator of this plugin) comment, the old way I was doing it was not necessary. So I have updated the code to reflect those changes and tested it. However if you do this, you will also have to add an extra Route to your config. Here is the one I added :
map.connect '/images/:action/:id/:style' , :controller => 'images'
Thanks Jon!
Hamza