We build Web & Mobile Applications.
For well over a year now attachment_fu has been my plugin of choice for adding file uploads to our Rails applications, but recently my fellow WebFellas have been raving about Paperclip from the clever guys at thoughtbot. As I’ve just started yet another new project I figured it was time to take Paperclip for a spin.
If, like me, you’re a long-suffering Rails developer using Windows (those new Macbooks are looking very tempting!) then you won’t be surprised to learn that getting Paperclip up and running needs a few special steps.
You can install Paperclip as a regular Rails plugin from GitHub from a command prompt window like this:
ruby script/plugin install git://github.com/thoughtbot/paperclip.git
You’ll obviously need a working copy of Git: thoughtbot switched off their SVN repository this week and the gemified version of Paperclip on RubyForge is quite old now and probably shouldn’t be used (gems via GitHub are not currently available).
Paperclip uses ImageMagick to perform image processing, which is thankfully much easier to install on Windows than ImageScience was for attachment_fu!
You’ve got two options:
Use the RMagick gem
Although Paperclip doesn’t use the RMagick gem, if you think you’re ever likely to then you should download the latest gem from RubyForge and use the version of ImageMagick that is bundled with it (see the Readme.html
file included in the download for installation instructions).
Download the latest version of ImageMagick
If you’re not bothered about RMagick support then download and install the ImageMagick installer (I used the 16 bits per pixel dynamic library without any problems).
Whichever installation method you choose make sure you select the ‘Update executable search path’ option in the ImageMagick installer.
Important: after looking into this a bit more closely I’m no longer 100% convinced that patching Tempfile is necessary for Paperclip. I’d recommend you first try using it without this patch and if you start to see problems then try the patch (and it’d also be great if you could post a comment to let me know that you needed the patch).
Paperclip makes extensive use of temporary files via the Ruby Tempfile
class. Unfortunately this can cause some strangeness in Windows where the file size is incorrectly reported as zero bytes (attachment_fu suffered with similar problems). The solution is to monkey-patch Tempfile
like this:
require 'tempfile'
class Tempfile
def size
if @tmpfile
@tmpfile.fsync
@tmpfile.flush
@tmpfile.stat.size
else
0
end
end
end
This code comes from Emmanuel Pirsch and has worked well for me in several projects. I typically drop it into my lib/patches
folder and require it from an initializer.
Make sure you shutdown any running servers, close any console windows and then reopen and restart as needed: it’s important that your consoles and servers pick up the updated PATH
environment variable set by the installer as Paperclip calls ImageMagick using the command line (which is why it doesn’t need RMagick).
At this point you should be ready to start using the plugin. I won’t cover the basics of using Paperclip with Rails as they’ve already been explained on the official project page as well as in Jim Neath’s excellent tutorial and more recently in a RailsCast from Ryan Bates. However there are some newer features and tips that you should know about to get the best out of Paperclip.
If you’ve used attachment_fu then you’re probably aware that older versions didn’t like attr_protected. The good news is that not only has attachment_fu recently been fixed, but Paperclip also plays nicely with both attr_protected and attr_accessible too. So to play it safe you should protect the file_name
, content_type
and size
attributes in your models, for example:
has_attached_file :logo
attr_protected :logo_file_name, :logo_content_type, :logo_size
Attaching a file to a model in a migration or when using the Rails console is much easier with Paperclip than it is with attachment_fu. All you need to do is open a File object and assign it to the attachment attribute of the model:
user = User.first
File.open('path/to/image.png') { |photo_file| user.photo = photo_file }
user.save
Of course that works on pretty much every operating system in the world except Windows, where the assignment will cause an error like this to be output in the console window:
identify: no decode delegate for this image format `C:/DOCUME~1/ROB~1.CML/LOCALS~1/Temp/stream.2692.0'.
In addition to this cryptic message, the file isn’t attached properly and no thumbnails are generated. Thankfully the solution is simple (although it did take me a few moments of head scratching to figure it out): set binary mode on the file to stop Windows treating it like a text file:
user = User.first
File.open('path/to/image.png', 'rb') { |photo_file| user.photo = photo_file }
user.save
When an image file is uploaded Paperclip stores it using the original
style name. While it isn’t possible to tell Paperclip to simply discard the original image, it is possible to define your own original
style which Paperclip will then use. As an example, if your model contains the following:
has_attached_file :photo,
:styles => { :original => '250x250>',
:small => '50x50' }
Then even if a huge photo (say 1600x1600) is uploaded, it won’t be stored on your server (or S3 bucket). You will however still have an original
file but it will have been resized to 250x250, helping to reduce the amount of storage space needed for attachments.
Most of the time you’ll probably be passing geometry strings for the has_attached_file :styles
option, but a very nice feature is the ability to also pass a Proc
to provide dynamic geometry string generation. For example, you might have a model that has photo_width
and photo_height
attributes that can be specified by the user and you want to generate a ‘custom’ thumbnail that uses these dimensions. Paperclip allows you to do this easily:
has_attached_file :photo,
:styles => { :original => '250x250>',
:small => '50x50',
:custom => Proc.new { |instance| "#{instance.photo_width}x#{instance.photo_height}>" } }
As you can see the Proc
receives the object that the attachment is part of as a parameter and returns a geometry string generated using the photo_width
and photo_height
attributes. Adding a call to the reprocess!
method of the attachment object in your model’s before_save
callback also allows you to regenerate the thumbnails if either of these attributes are updated.
The has_attached_file :url
and :path
options can be used to customise the name of your attachments: :url
defines the URL that will be used by things like image_tag
to access your image and allows you to either provide direct access to the image or to route access through a controller (to provide permission checking for example) and :path
determines where the image file is stored either on your server (for file system storage) or in an S3 bucket.
Both options use interpolation, allowing you to use special tags that will be replaced with actual values at runtime (just like regular Ruby string interpolation). The default interpolations provided by the plugin are:
:rails_root
:rails_env
:class
:basename
:extension
:id
:id_partition
:id
but formatted as a string using ID partitioning.:attachment
has_attached_file
) downcased and pluralised for your enjoyment.:style
:style
would be one of ‘original’ or ‘small’)The default :url
and :path
options for has_attached_file
are:
:url => "/:attachment/:id/:style/:basename.:extension"
:path => ":rails_root/public/:attachment/:id/:style/:basename.:extension"
Let’s say you’d prefer your users’ photos to be stored in a single ‘photos’ subdirectory of the public/images
folder on your server using the user ID and style as the base file name. Your model would need to contain something like this:
has_attached_file :photo,
:url => "/images/:attachment/:id_:style.:extension",
:path => ":rails_root/public/images/:attachment/:id_:style.:extension"
If you want to hide images behind a controller do something like this:
has_attached_file :photo,
:url => "/:class/:id/:attachment/:style.:extension",
:path => ":rails_root/attachments/:class/:id/:attachment/:style.:extension"
In this example the URL points to a PhotosController
nested within the User
resource (an example URL would be /users/2/photos/small.png
) and the attachment files are stored outside of the public root in a subdirectory of an attachments folder (e.g. RAILS_ROOT/attachments/users/2/photos/small.png
). The show action of the PhotosController
would be responsible for returning the binary data of the appropriate file using the :style
, :extension
and :user_id
parameters.
In addition to the predefined interpolations described above, Paperclip makes it very easy to define your own. For example one of my models has a symbol
attribute that I want to use in the file name of images attached to it, so in a Rails initializer I add the following code:
Paperclip::Attachment.interpolations[:symbol] = proc do |attachment, style|
attachment.instance.symbol.downcase
end
Interpolations are Procs
that take two parameters, the attachment object and the current style, and it is possible to access the model that the attachment is part of using the instance
attribute of the attachment object.
After adding my custom interpolation I can then use it like this:
has_attached_file :logo,
:url => '/:attachment/:symbol/:style/:basename.:extension',
:path => ':rails_root/public/:attachment/:symbol/:style/:basename.:extension '
Deleting an existing attachment from a model is as simple as setting the attachment attribute to nil
. In a RESTful world you could do this from the destroy
action of a controller that maps to the attachment (for example using a DELETE
request on /users/1/photos
).
You can also quite easily replace an existing attachment by POSTing a new file to your update action. Things get a little trickier if you want to be able to delete an existing attachment without replacing it using an update action.
The approach I’ve used is to add a checkbox to the edit form that when checked causes any existing attachment to be removed unless a new file has also been selected in the file upload box. Here’s the view code:
<% form_for(user, :html => { :multipart => true }) do |f| %>
<div>
<%= f.label(:photo, 'Upload photo') %>
<%= f.file_field(:photo) %>
</div>
<%- unless user.new_record? || !user.photo? -%>
<div>
<%= f.label(:delete_photo, 'Delete photo') %>
<%= image_tag(user.photo.url, :alt => 'Photo', :title => 'Current photo') %>
<%= f.check_box(:delete_photo) %>
</div>
<%- end -%>
<% end %>
This adds a checkbox, using an instance variable to track its value, if the user isn’t new and already has a photo. The instance variable is added to the model along with a callback to clear the photo:
before_validation :clear_photo
def delete_photo=(value)
@delete_photo = !value.to_i.zero?
end
def delete_photo
!!@delete_photo
end
alias_method :delete_photo?, :delete_photo
protected
def clear_photo
self.photo = nil if delete_photo? && !photo.dirty?
end
Then the update action in the controller looks like this:
def update
if @user.update_attributes(params[:user])
flash_and_redirect_to('User profile was saved successfully.', :notice, users_path(@user))
else
render(:action => 'edit')
end
end
A module containing a reusable version of this code is available in this gist. The module can be included in an initializer and adds the has_deletable_attachment
method to your models. You can then do something like this:
class MyModel < ActiveRecord::Base
has_attached_file :photo, ... # paperclip options
has_deletable_attachment :photo
# rest of model
end
Paperclip allows you to validate the presence, content type and size of an attachment file using the validates_attachment_presence
, validates_attachment_content_type
and validates_attachment_size
methods.
But what if you want to do something more advanced? For example let’s say we have a Track
model that represents uploaded MP3 files and, because we want to preserve the musical integrity of our site, we want to prevent files from certain ‘artists’ being uploaded. To do this we can use a custom validation method:
class Track < ActiveRecord::Base
has_attached_file :mp3
validates_attachment_presence :mp3
validates_attachment_content_type :mp3, :content_type => [ 'application/mp3', 'application/x-mp3', 'audio/mpeg', 'audio/mp3' ]
validates_attachment_size :mp3, :less_than => 10.megabytes
validate :must_have_valid_artist_tag
protected
def must_have_valid_artist_tag
Mp3Info.open(mp3.to_file.path) do |mp3info|
errors.add(:mp3, 'must not be a Michael Bolton song (what are you thinking?!)') if mp3info.tag.artist == 'Michael Bolton'
end if mp3?
rescue Mp3InfoError => e
errors.add(:mp3, "unable to process file (#{e.message})")
end
end
This model makes use of the ruby-mp3info gem to access the ID3 tags: you’ll need to install it and set up the necessary config.gem
line in your environment.rb
file to get this example working.
The first four lines of the model are straight calls to Paperclip methods: they setup the attachment and ensure it is validated for presence, content type and size. The model then contains a validate
callback for the must_have_valid_artist_tag
method: this is where the good stuff happens.
Here’s a line-by-line breakdown:
If a new MP3 file has been attached then at the time of validation it won’t have been written to the correct location on the server (or S3 bucket). Fortunately the to_file
method means we don’t have to worry about this: it returns a File
object that will either refer to an existing attachment file or the new, temporary, uploaded file. The first line of the validation method passes the path name of this file to the Mp3Info
class so that it can process it.
In this simple example the validation checks if the artist
tag of the MP3 file is set to a particular artist and flags an error as appropriate.
Before doing any of the above it’s a good idea to check that a file was actually attached: Paperclip provides a simple way to do this by calling the mp3?
method which returns true
if a file has been attached. The name of this method is based on the name of your attachment, so for example if your model contains has_attached_file :photo
then the photo?
method will be used check for an attached file.
The Mp3Info
class will raise an Mp3InfoException
if something goes wrong. We need to rescue it in case an invalid MP3 file is uploaded.
To keep this example simple, if an exception occurs an error is added to the mp3
attribute containing the exception message: you could naturally do something more impressive here.
By providing you with direct access to the attachment file using the to_file
method, Paperclip enables you to do pretty much anything with the attachment, either in a validation like the one shown above or in a different model callback such as before_save
.
Being hosted on GitHub naturally means that other developers are forking Paperclip and changing it to suit their needs. We’re no exception and Chris has recently been going forking crazy, adding support for thumbnailing of video attachments amongst other things, so I’ll try and nudge him into blogging about his changes.
Even though I’ve only been using Paperclip for a couple of days I really like its straightforward, flexible approach and I’ll definitely be using it for future projects (that is until something newer and shinier comes along). However my old friend attachment_fu is far from dead and so I’ll be keeping watch on its development too.
Can the path to ImageMagick be configured?
If you’d prefer not to rely on the search path you can explicitly configure the location of ImageMagick in an initializer:
Paperclip.options[:image_magick_path] = 'Path/to/ImageMagick'
Can thumbnail errors be reported?
The whiny_thumbnails
option can be enabled to see errors when thumbnailing goes wrong:
Paperclip.options[:whiny_thumbnails] = true
How can
validates_attachment_content_type
be used with image uploads?
If you have a photo
attachment and only want to allow gif
, jpeg
and png
uploads you can do this:
validates_attachment_content_type :photo, :content_type => [ 'image/gif', 'image/png', 'image/x-png', 'image/jpeg', 'image/pjpeg', 'image/jpg' ]
Can a specific file format be used for a thumbnail?
By default thumbnails are created using the same image format as the original file, however it is possible to specify the format as shown below:
class User < ActiveRecord::Base
has_attached_file :avatar,
:styles => { :square => ["64x64#", :png],
:small => "150x150>" }
end
In this example the square
thumbnail has been defined using an array containing a geometry string and a format specifier. If you were to upload a GIF file the small
thumbnail would also be a GIF but the square
thumbnail would be converted to PNG.
Can a model have multiple attachments?
You can call has_attached_file
multiple times in a model to define multiple attachments.
Alternatively you could create an Attachment
model that can be shared (via a polymorphic association) across multiple models. If you take this approach you must explicitly define the attachment class name when defining the association, otherwise you’ll get an error when you try and access the association like this:
NoMethodError: undefined method `quoted_table_name' for Paperclip::Attachment:Class
For example:
has_many :attachments, :class_name => '::Attachment'