Paperclips
One of the main features in the Todobebe web sites is photo uploads. We have already improved the process a few times(refactoring FTW) and in this post I selected some recipes based in our experience:

  • asynchronous upload to s3
  • delaying the thumbnail generation
  • anonymous photos
  • saving photo dimensions

If you are using paperclip, some of the following recipes may be useful for you.

About Paperclip and Installation

Paperclip is a file upload plugin for Rails created by the thoughtbot (same guys who created shoulda and factory_girl). Paperclip is very active in github. Let it be known that it is the forth or fifth file upload plugin that I’ve already used and the best one so far IMHO.

Its installation is simple. In your config/environment.rb, add:

1config.gem 'paperclip', :source => 'http://gemcutter.org'

Those using old paperclip versions may not have noticed it can be installed as a gem now (what is recommended by its developers).

Basic usage

If you just want to save a file in the file system:

1class User < ActiveRecord::Base
2    has_attached_file :avatar, :styles => { :medium => "120x120>", :thumb => "80x80>" }
3 end

and for s3 storage (with a thumbnail):

1class User < ActiveRecord::Base
2  has_attached_file :avatar, :storage => :s3,
3    :styles => { :medium => "120x120>", :thumb => "80x80>" },
4    :s3_credentials => "#{RAILS_ROOT}/config/amazon_s3.yml",
5    :bucket => "papertest",
6    :path => ":class/:id/:basename_:style.:extension"
7end

That’s it! If you need to improve the users’ experience and don’t want make them wait for: (1)the thumbnail generation and (2)the upload to s3 after they already have waited (3)the upload to your server, a bit more code will be necessary.

Asynchronous upload to s3

This recipe uses the gem delayed_job which can be easily replaced by other background job solution such as workling or a rake task + a cron job. If you don’t know delayed_job, there is a nice railscast about it. Assuming you have delayed_job installed, let’s start our example:

1class User < ActiveRecord::Base
2    has_attached_file :local_avatar, :styles => { :medium => "120x120>", :thumb => "80x80>" }
3    has_attached_file :remote_avatar, :storage => :s3,
4      :styles => { :medium => "120x120>", :thumb => "80x80>" },
5      :s3_credentials => "#{RAILS_ROOT}/config/amazon_s3.yml",
6      :bucket => "papertest",
7      :path => ":class/:id/:basename_:style.:extension"
8 end

The basic idea is to upload to the filesystem(local_avatar) and in background to upload to s3(remote_avatar). In the form there will be something like this:

1<%= f.file_field :local_avatar %>

Nothing new so far. Now we’ll use a ActiveRecord callback to schedule the upload to s3:

1after_save :queue_upload_to_s3
2def queue_upload_to_s3
3  send_later(:upload_to_s3) if self.local_avatar_updated_at_changed?
4end
5def upload_to_s3
6  self.remote_avatar = local_avatar.to_file
7  self.save!
8end

And if you don’t want to keep a local copy, just change this method:

1def upload_to_s3
2  self.remote_avatar = local_avatar.to_file
3  self.local_avatar = nil
4  self.save!
5end

Another tip it’s to create an alias to forget that you are working with two attachments:

1alias_method :avatar=, :local_avatar=
2def avatar
3  self.remote_avatar? ? self.remote_avatar : self.local_avatar
4end

In the views you will do:

1<%= f.file_field :avatar %>
2 
3<%= image_tag @user.avatar.url(:thumb) %>

I really like this solution because it’s simple and non intrusive.
Alternatives:

Delaying the thumbnail generation

For this one I’ve made a mix of two solutions that I found:

First of all, it’s necessary to add a column (processsing, for instance) to flag that the thumbnail isn’t ready. Once you’ve done that, we have to prevent the thumbnail generation.

1before_local_avatar_post_process do |image|
2  if image.dirty?
3    image.processing = true
4    false # halts processing
5   end
6end

That will prevent any image processing by paperclip. Now we just need to implement a method to let the paperclip to do it and put on background, in this recipe using the delayed_job:

01after_save :queue_process_styles
02 
03def queue_process_styles
04  send_later(:process_styles) if self.local_avatar.dirty?
05end
06 
07def process_styles
08  self.local_avatar.reprocess!
09  self.processing = false
10  self.save(false)
11end

And for placeholder image to show the thumbnails during the processing, just use the option default_url:

1has_attached_file :local_avatar, :styles => { :medium => "120x120>", :thumb => "80x80>" },
2  :default_url =>  ":class/:attachment/:style/processing.png"

Alternative:

Anonymous photos

To implement it, we have to customize the file path using the options path and url:

1has_attached_file :local_avatar,
2  :url => "/system/:attachment/:id/:style/:filename",
3  :path => ":rails_root/public:url"

The value of each of those words beginning with : is determined by a method of the Paperclip::Interpolations. The :attachment, for instance, returns the pluralized name of the attachment as given to has_attached_file. To create one is very simple:

1Paperclip.interpolates :anonymous do |attachment, style|
2  Digest::SHA1.hexdigest("#{attachment.instance.id}")
3end

Back to our example:

1has_attached_file :local_avatar,
2  :url => "/system/:attachment/:id/:style/:anonymous.:extension",
3  :path => ":rails_root/public:url"
4#example:
5#/system/local_avatars/12/original/7b52009b64fd0a2a49e6d8a939753077792b0554.jpg

I don’t know if SHA-1 is the best way to do it and I would like to receive suggestions to improve this code.

Alternatives:

  • :id
  • :id_partition
  • :timestamp
  • an interpolation created by you

Photo dimensions

The class Paperclip::Geometry has methods to get the image dimensions. We just need to add the columns(width and height, for instance) and set their values using an appropriate callback:

01before_save :set_dimensions
02 
03def set_dimensions
04  tempfile = self.local_avatar.queued_for_write[:original]
05 
06  unless tempfile.nil?
07    dimensions = Paperclip::Geometry.from_file(tempfile)
08    self.width = dimensions.width
09    self.height = dimensions.height
10  end
11end

That’s it!

Uploaded successfully!

I wanted to share the above recipes as I figure they are useful to those working in similar projects. I’d like to thank those who have worked on the projects mentioned above: Christopher Saylor, Bruno Miranda and Dante Regis.
Thank you for reading!

Photo by williac


Be the first to like this post.

13 Responses to “Paperclip Recipes”

  1. 1 Foong

    Hi, great post! I wonder if you have any experience on how to handle preview of uploaded images before it is saved. For example:

    I have a form that allow user to create article and at the same time user can attach a photo/image in the same form. when the user submit the form the article and the attached image will be show on a article preview page (without saving it to database). It is only saved if user click “save” on the preview page. It is easy to do preview for textfield or textarea data but I am not sure how to handle the image.

    did you come across situation like this?

    Thanks,
    Foong

    • Hi Foong,

      I’ve already worked with preview of uploaded images, but in my case I needed to save a temporary file and show its thumbnail in the form using AjaxUpload. Look at my solution, I think it will help you: http://gist.github.com/294561

      Best,
      Roberto Soares

      • 3 Foong

        Thanks Roberto,

        It really helpful !

        Thanks a lot,
        Foong

      • 4 RV

        Hello,
        Thanks for your code snippet and it is helpful.
        However,when I try out your code ( tempupload.rb),i get errors :
        @uploaded_file.original_filename ( origina_filename is not a method) and
        @uploaded_file.to_tempfile ( to_tempfile is not a method).

        how is the tempupload.rb supposed to be used- is it a monkey patch to paperclip ??

        thanks for your help.

      • 5 nktokyo

        This seems exactly what I’m looking for but I can’t figure out how to incorporate it properly. Do you have a sample app that uses this?

  2. 6 Tair

    Thanks for a great post. I will definitely try async upload to S3 as you explained.

    Cheers.

  3. 8 Anthony

    This is exactly what I’m looking for but have ran into a bit of a problem. The self.local_avatar.dirty? never validates to true after save. Not sure exactly whats going on here but if you have any thoughts that would be great. If I comment out that check to always send_later I end up with a constant connection to s3 that continually uploads. It will save the first file but then get stuck in a loop uploading the file to s3 with a new ID. No idea whats going on here!

    Thanks!

    • Hi Anthony,
      You are right, I’ve replaced ‘self.local_avatar.dirty?’ by ‘self.local_avatar_updated_at_changed?’ and I think it’ll work fine now. I created a repository with the solution, so you can fork and help me to improve it: http://github.com/roberto/paperclip_recipes.
      Thanks!

      • 10 Anthony

        Thanks for the reply. That should work perfectly. I’m not sure how I missed this the first time but the reason for the continuous uploading to s3 that I mentioned is because when I commented out the “self.local_avatar.dirty?” it was caught in a constant loop. It would call the “after_save :queue_upload_to_s3″ then in “upload_to_s3″ save the object again which would go right back to the after save and back to “upload_to_s3″. I’m sure this would be obvious to most but I spent a few hours trying to figure out why this was happening! It’s ok to laugh…

        Thanks for great post!

  4. hey, great bunch of recipes. made me want to keep using paperclip instead of rolling my own. i have a snag. if the above code covering preventing thumbnail generation it’s not clear where this block goes…

    before_local_avatar_post_process do …

    is that placed as a class method of the paperclipped model or is it part of some previously defined callback? it’s not immediately clear and i get a no method error when i place it as a class method of the paperclipped model.

    thanks.


  1. 1 andHapp » Paperclip and validate the attachment dimension:
  2. 2 Quora

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>