Uploading Files with Paperclip

Share this article

Screenshot 2015-07-01 08.53.31

Some time ago I wrote Better File Uploads with Dragonfly and Asynchronous File Uploads in Rails, two articles that covered Dragonfly, a nice gem to handle various attachments. In this article I am going to introduce Paperclip by Thoughtbot – probably, the most popular and feature-rich solution for integrating file uploading and management into an application.

One of the great things about Paperclip is its large and active community – this gem is constantly being updated and that’s really important. You don’t want to use a solution that will be abandoned in a year, do you?

We are going to observe all the main features of Paperclip and create a pretty simple but useful app featuring:

  • Basic file uploading
  • Validations and callbacks
  • Post-processing (with thumbnail generation)
  • Amazon S3

I will give you all necessary info to start off really quick and also present links for further reading.

The source code for the demo app can be found on GitHub.

The working example of the demo app can be accessed at sitepoint-paperclip-uploader.herokuapp.com.

Some Preparations

We are going to craft an app that allows users to upload their own photos, as well as browse photos added by others.

Create a new Rails app called SimpleUploader:

$ rails new SimpleUploader -T

I am using Rails 4 here, but Paperclip is compatible with Rails 3.2 and even Rails 2 (just use Papeclip 2.7, in that case).

I am going to use Bootstrap for basic styling, but you may skip this step:

Gemfile

[...]
gem 'bootstrap-sass'
[...]

Run

$ bundle install

And update the necessary style files:

application.scss

@import 'bootstrap-sprockets';
@import 'bootstrap';

Now, tweak the layout to include a top menu and style the main content:

layouts/application.html.erb

[...]
<nav class="navbar navbar-inverse">
  <div class="container">
    <div class="navbar-header">
      <%= link_to 'Simple Uploader', root_path, class: 'navbar-brand' %>
    </div>
    <div id="navbar" class="collapse navbar-collapse">
      <ul class="nav navbar-nav">
        <li><%= link_to 'Photos', root_path %></li>
        <li><%= link_to 'Upload Your Photo', new_photo_path %></li>
      </ul>
    </div>
  </div>
</nav>

<div class="container">
  <% flash.each do |key, value| %>
    <div class="alert alert-<%= key %>">
      <%= value %>
    </div>
  <% end %>

  <%= yield %>
</div>
[...]

We are only going to have a single controller:

photos_controller.rb

class PhotosController < ApplicationController
end

Set up the corresponding routes:

config/routes.rb

[...]
resources :photos, only: [:new, :create, :index, :destroy]

root to: 'photos#index'
[...]

Let’s create our Photo model. For now, add just a title attribute… we’ll take care of the other ones later:

$ rails g model Photo title:string
$ rake db:migrate

Basically, the preparations are done. Now we can integrate Paperclip and flesh out the controller. On to the next section!

Implement File Upload in 60 seconds

System Requirements

Before proceeding, spend a couple of minutes to check if your system is ready for Paperclip. A few things have to be done:

  • Ruby 2.0 or higher has to installed and Rails 3.2 or 4 has to be used. You may still use Ruby 1.8.7 or Rails 2, but for that, stick to Paperclip 2.7. This version has a separate readme and I am not going to cover it here.
  • Windows users have to install DevKit.
  • ImageMagick, a great utility to work with images, has to be installed. Binary releases for all major platforms are available.
  • The file command should be available. Windows does not have it, but DevKit provides it. Under some circumstances, however, you will have to add it manually – read more here.

When all items in this small checklist are done, proceed to the next step!

Integrating Paperclip

You do not have to be a superhero to integrate file uploading functionality into your app in one minute – just use Rails and Paperclip. Seriously, it is really easy to get started. First of all, include the gem:

Gemfile

[...]
gem "paperclip", "~> 4.2"
[...]

and install it:

$ bundle install

Now, we need to add some attributes to the photos table. You may generate a simple migration for this, but Paperclip has a nice generator:

$ rails generate paperclip photo image

Here is what the resulting migration looks:

xxx_add_attachment_image_to_photos.rb

class AddAttachmentImageToPhotos < ActiveRecord::Migration
  def self.up
    change_table :photos do |t|
      t.attachment :image
    end
  end

  def self.down
    remove_attachment :photos, :image
  end
end

As you can see, Paperclip even presents its own attachment method and that’s really convenient. What does this method actually do? It adds the following columns into your table:

  • {attachment}_file_name
  • {attachment}_file_size
  • {attachment}_content_type
  • {attachment}_updated_at

The name of the attachment field is the argument provided to the attachment method (image in our case). Actually, only the {attachment}_file_name field is required, so if you don’t want to store other information, go ahead and create a basic migration instead.

Now apply the migration:

$ rake db:migrate

and equip your model with Paperclip functionality:

models/photo.rb

[...]
has_attached_file :image
[...]

Set up the controller:

photos_controller.rb

class PhotosController < ApplicationController
  def index
    @photos = Photo.order('created_at')
  end

  def new
    @photo = Photo.new
  end

  def create
    @photo = Photo.new(photo_params)
    if @photo.save
      flash[:success] = "The photo was added!"
      redirect_to root_path
    else
      render 'new'
    end
  end

  private

  def photo_params
    params.require(:photo).permit(:image, :title)
  end
end

Note that you only have to permit image, not image_file_name or other fields. Those are used internally by Paperclip.

The view for the new action:

views/photos/new.html.erb

<div class="page-header"><h1>Upload Photo</h1></div>

<%= form_for @photo do |f| %>
  <%= render 'shared/errors', object: @photo %>

  <div class="form-group">
    <%= f.label :title %>
    <%= f.text_field :title, class: 'form-control' %>
  </div>

  <div class="form-group">
    <%= f.label :image %>
    <%= f.file_field :image, class: 'form-control'%>
  </div>

  <%= f.submit 'Upload', class: 'btn btn-primary' %>
<% end %>

I prefer to extract error messages to a separate partial:

views/shared/_errors.html.erb

<% if object.errors.any? %>
  <div class="panel panel-warning errors">
    <div class="panel-heading">
      <h5><i class="glyphicon glyphicon-exclamation-sign"></i> Found errors while saving</h5>
    </div>

    <ul class="panel-body">
      <% object.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>

Okay, so as you can see, the form is really simple. Just use the f.file_field :image to render the appropriate field. For Rails 3, you’ll also have to provide :html => { :multipart => true } to the form_for method.

However, if you try to upload a file now, an exception will be raised. Why? Because Paperclip cares about the security of your app like no one else does! Starting from version 4, it is required to have at least a content type validation in your model, so let’s add it:

models/photo.rb

[...]
validates_attachment :image,
                     content_type: { content_type: ["image/jpeg", "image/gif", "image/png"] }
[...]

Please note that, for this validation to work, your table must have an _content_type column.

But, what if we’re seasoned developers and can take care of ourselves? Can we tell Paperclip that our attachments should not be validated? But, of course:

do_not_validate_attachment_file_type :avatar

However, add this line only if you really understand what you are doing.

With validations in place (or after disabling it explicitly) you can upload your first file. Before doing that, however, let’s create the index view:

views/photos/index.html.erb

<div class="page-header"><h1>Photos</h1></div>

<%= render @photos %>

We are simply iterating over the @photos array. The _photo.html.erb partial will be used by default, so let’s create it:

views/photos/_photo.html.erb

<div class="media">
  <div class="media-left">
    <%= link_to image_tag(photo.image.url, class: 'media-object'), photo.image.url, target: '_blank' %>
  </div>
  <div class="media-body">
    <h4 class="media-heading"><%= photo.title %></h4>
  </div>
</div>

To display an image we are using the good old image_tag method. image.url, as you’ve guessed, returns the URL to the original image.

Go ahead and upload something. Everything should be working fine, but there is more to be done, so let’s proceed to the next step.

Generating Thumbnails and Post-Processing

If you upload a large image, it will totally break your page layout. We could apply some simple styles to shrink large images inside .media blocks, but that’s not quite optimal. Users will still have to download the full version of the image that will then be resized. Why waste bandwidth like this? Let’s generate thumbnails instead with the help of ImageMagick!

Each attachment can have a set of “styles” that define various versions of an original image. Let’s add one:

models/photo.rb

[...]
has_attached_file :image,
                  styles: { thumb: ["64x64#", :jpg] }
[...]

What does this all mean? thumb is the name of the “style”. 64x64, as you’ve guessed, means that this version of the image be sized to 64×64 pixels. # means that ImageMagick should crop the image if it is larger than the provided dimensions (with center gravity).

If you browse this page you won’t find this option, but Paperclip adds it on its own. By the way, Dragonfly does the same thing.

:jpg means that the original file should be converted to a JPEG. Really neat and clean.

You can re-generate thumbnails according to the new styles with the following command:

$ rake paperclip:refresh:thumbnails CLASS=User

To re-generate all images (including original ones) use:

$ rake paperclip:refresh CLASS=Photo

To generate only the missing images:

$ rake paperclip:refresh:missing_styles

Now, update the partial:

views/photos/_photo.html.erb

[...]
<%= link_to image_tag(photo.image.url(:thumb), class: 'media-object'), photo.image.url, target: '_blank' %>
[...]

We just provide the style’s name to the url method. Check out the result!

Let’s add some more post-processing options:

models/photo.rb

[...]
has_attached_file :image,
                  styles: { thumb: ["64x64#", :jpg],
                            original: ['500x500>', :jpg] },
                  convert_options: { thumb: "-quality 75 -strip",
                                     original: "-quality 85 -strip" }
[...]

original means that the following styles need to be applied to the original image. 500x500> means that the image should be resized to 500×500 and shrunk, if required. If the image’s dimensions are smaller, it will be left intact.

With the help of convert_options you can pass additional parameters. In this example, we say that the image quality should be rescaled to 75% (thumb) and 85% (original) accordingly to reduce their size. The -strip option tells ImageMagick to strip out all meta information.

Regenerate all images:

$ rake paperclip:refresh CLASS=Photo

and observe the results.

You can easily define styles dynamically by passing lambda to styles:

has_attached_file :image, :styles => lambda { |attachment| { :thumb => (attachment.instance.title == 'Special' ? "100x100#" : "64x64#") } }

In this pretty naive example, we simply check the photo’s title and, if it is “Special”, use other dimensions for styling.

Paperclip presents you with convenient callbacks before_post_process and after_post_process to do work before and after post-processing. You may even define attachment-specific callbacks:

before_{attachment}_post_process
after_{attachment}_post_process

The classic example for callbacks usage is checking whether the file is an image or not. Paperclip’s documentation presents the following snippet:

before_post_process :skip_for_audio

def skip_for_audio
  ! %w(audio/ogg application/ogg).include?(asset_content_type)
end

If the before callback returns false, post-processing won’t occur. Read more here.

It’s worth mentioning that you can write your own processor and store it inside lib/paperclip_processors – Paperclip automatically loads all files placed here. Read more here.

Validations

Let’s return to our validations and add a couple more. For example, I want all photos to have their images set and their sizes to be no more than 500 KB. That’s easy:

models/photo.rb

validates_attachment :image, presence: true,
                     content_type: { content_type: ["image/jpeg", "image/gif", "image/png"] },
                     size: { in: 0..500.kilobytes }

By the way, Paperclip also automatically prevents content type spoofing – that is, you can’t upload HTML file as JPEG for example. More can be found here.

Another thing to remember is that post-processing won’t start if the model is not valid, that is if validation failed.

Storing the Images

By default all images are placed inside the public/system directory of your Rails project. You probably don’t want users to see that path and obfuscate it somehow. Paperclip can take care of it as well!

models/photo.rb

[...]
has_attached_file :image,
                  url: "/system/:hash.:extension",
                  hash_secret: "abc123"
[...]

hash_secret has to set for this to work.

Another question you might ask is, what happens to the images when the corresponding record is destroyed? Find that out by adding a destroy method to the controller

photos_controller.rb

[...]
def destroy
  @photo = Photo.find(params[:id])
  @photo.destroy
  flash[:success] = "The photo was destroyed."
  redirect_to root_path
end
[...]

and modifying the partial:

views/photos/_photo.html.erb

<div class="media">
  <div class="media-left">
    <%= link_to image_tag(photo.image.url(:thumb), class: 'media-object'), photo.image.url, target: '_blank' %>
  </div>
  <div class="media-body">
    <h4 class="media-heading"><%= photo.title %></h4>
    <%= link_to 'Remove', photo_path(photo), class: 'btn btn-danger', method: :delete, data: {confirm: 'Are you sure?'} %>
  </div>
</div>

Remove some photos – the corresponding images should be destroyed as well. This behavior, however, can be changed by setting preserve_files to true:

models/photo.rb

[...]
has_attached_file :image,
                  preserve_files: "true"
[...]

This is convenient when using something like actsasparanoid for soft deletion of records.

Sitting on the Cloud

Can we store images in a CDN, you ask? Yes, yes we can. In this demo, I am going to show you how to use Amazon S3 as file storage. Paperclip does support other storage options, like Dropbox.

Drop in the new gem:

Gemfile

[...]
gem 'aws-sdk', '~> 1.6'
[...]

and run

$ bundle install

At the time of this writing, Paperclip did not support AWS SDK version 2, so don’t use it yet! Support for it should soon be added though.

Now modify your model (remove url and hash_secret options as well):

models/photo.rb

[...]
has_attached_file :image,
                  styles: { thumb: ["64x64#", :jpg],
                            original: ['500x500>', :jpg] },
                  convert_options: { thumb: "-quality 75 -strip",
                                     original: "-quality 85 -strip" },
                  storage: :s3,
                  s3_credentials: {access_key_id: ENV["AWS_KEY"], secret_access_key: ENV["AWS_SECRET"]},
                  bucket: "YOUR_BUCKET"
[...]

Actually, s3_credentials may accept a path to a YAML file with access_key_id and secret_access_key. What’s more, you can provide different values for various environments like this:

development:
  access_key_id: 123...
  secret_access_key: 123...
test:
  access_key_id: abc...
  secret_access_key: abc...
production:
  access_key_id: 456...
  secret_access_key: 456...

That would an overkill in our case, though. Read more here.

Now, where to get those keys? Register at AWS and navigate to Security Credentials (dropdown menu at the top right corner). Next open the Users tab and click the Create New Users button. We are going to create a special service user account that will only have permissions to work with S3. Prior to introducing this system, we had to use the root key, which sn’t very secure. Anyone who gains access to this key had full access to all your AWS services. Root keys are still supported by Amazon, but I really don’t recommend using them.

After clicking on the Create New Users enter the user’s name and check the “Generate an access key for each user”. Click Create.

Now click “Show User Security Credentials”, copy and paste the Access Key ID and Secret Access Key into your model. This will be the last time the user’s credentials will be available for download, so you may also want to download and store them in a secure place. Be very careful with this key pair and never expose it (specifically, it should not be visible in GitHub). When deploying on Heroku, I am using environment variables to store this key pair.

When you are done, go to the Groups page and click Create New Group. Enter the group’s name (something like “accesstos3″).

Click Next and enter “S3” in the search form on the next page. This is the page to select policies for the group. Choose “AmazonS3FullAccess” and click Next and Create. By the way, you can create your own policies on the corresponding page to define custom permissions (for example, if you want to grant someone access to billing information – there is no such policy by default).

Click on the newly create Group and click Add Users to Group. Next, just select your new user from the list and click Add Users. Now your user has all the necessary permissions to work with S3. If you’ve worked with Active Directory before, the process should be very familiar.

Also do not forget to open the S3 Management Console and create a new bucket to store your images (you may think of buckets as simple folders). Provide the bucket’s name in the bucket option in your model file.

Basically, you are good to go. Try uploading some files using your new storage!

Please note that, by default, those files are being loaded with public_read permissions, which means that anyone can view them simply by entering the correct URL. Using the s3_permissions option you can change that (Here is the information about S3 permissions).

Paperclip’s wiki also presents a nice guide on restricting access to S3 images. Basically it suggests setting s3_permissions to private and using a special download action in the controller with expiring URL.

The last thing to note is that, in general, S3 is a paid service. For the first year, however, you get 5GB of storage space, 20000 GET, and 2000 PUT requests per month for free. Full pricing information can be found here.

Conclusion

In this article we’ve discussed Paperclip and its various features, like post-processing, callbacks, URL obfuscation, and support for various storage options. If you’ve never used Paperclip in your projects, I really recommend giving it a try when a need for file uploading support arises.

As always, feedback is very much welcome and don’t hesitate to ask your questions. Thanks for reading and see you soon!

Frequently Asked Questions (FAQs) about Uploading Files with Paperclip

What is Paperclip and why is it used in Rails?

Paperclip is a file attachment library for Active Record, a component of Ruby on Rails. It provides a set of functionalities that help in handling file uploads to the server. Paperclip can process, validate and store files on different storage backends including the local file system, Cloud-based storage services like Amazon S3, and more. It’s widely used due to its simplicity, flexibility, and robustness.

How do I install Paperclip in my Rails application?

To install Paperclip, you need to add the following line to your application’s Gemfile: gem 'paperclip'. Then execute bundle install to install the gem. After that, you need to add the necessary fields to your model using a migration. For example, if you’re adding an image to a User model, you might add a migration like this: rails generate migration add_avatar_to_users avatar:attachment.

How can I validate the size and type of files uploaded with Paperclip?

Paperclip provides built-in validations for file size and content type. You can add these validations to your model. For example, to validate that an uploaded image is no larger than 1MB and is a JPEG or PNG, you could use the following code:

validates_attachment :image,
content_type: { content_type: ["image/jpeg", "image/png"] },
size: { in: 0..1.megabytes }

How can I process images with Paperclip?

Paperclip integrates with the ImageMagick command line tool, which allows you to process images in various ways. For example, you can resize images, convert them to different formats, and more. To do this, you need to specify the processing options in your model. For example, to resize an image to a width of 300 pixels, you could use the following code:

has_attached_file :image, styles: { medium: "300x300>" }

How can I use Paperclip with Amazon S3?

To use Paperclip with Amazon S3, you need to configure Paperclip to use S3 as the storage backend. This involves setting the :storage option to :s3 and providing your S3 credentials. Here’s an example:

has_attached_file :image,
storage: :s3,
s3_credentials: {
bucket: 'your_bucket_name',
access_key_id: 'your_access_key_id',
secret_access_key: 'your_secret_access_key'
}

How can I handle errors with Paperclip?

Paperclip provides a errors method that you can use to handle errors. This method returns an array of error messages. You can display these messages to the user to inform them of any issues with their file upload. For example, you might display the errors in your view like this:

<% if @user.errors.any? %>
<% @user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
<% end %>

How can I test Paperclip uploads in Rails?

You can test Paperclip uploads in Rails using the built-in testing framework, or with other testing libraries like RSpec. You can create a test file, attach it to your model, and then assert that the file was attached successfully. Here’s an example using RSpec:

it "attaches a file" do
user = User.new
user.avatar = File.new("path/to/your/test/file")
expect(user.avatar).to be_attached
end

How can I remove a file attached with Paperclip?

To remove a file attached with Paperclip, you can use the destroy method. This will remove the file from the storage backend and clear the attachment fields in your model. Here’s an example:

@user.avatar.destroy

How can I display a file uploaded with Paperclip in my views?

To display a file uploaded with Paperclip in your views, you can use the url method. This method returns the URL of the uploaded file. For images, you can use this URL in an image tag. Here’s an example:

<%= image_tag @user.avatar.url %>

How can I migrate from Paperclip to Active Storage?

Migrating from Paperclip to Active Storage involves several steps. First, you need to install Active Storage by running rails active_storage:install. Then, you need to create a migration to move your Paperclip data to the Active Storage tables. Finally, you need to update your models and views to use Active Storage instead of Paperclip. It’s recommended to test this process thoroughly before deploying it to a live application.

Ilya Bodrov-KrukowskiIlya Bodrov-Krukowski
View Author

Ilya Bodrov is personal IT teacher, a senior engineer working at Campaigner LLC, author and teaching assistant at Sitepoint and lecturer at Moscow Aviations Institute. His primary programming languages are Ruby (with Rails) and JavaScript. He enjoys coding, teaching people and learning new things. Ilya also has some Cisco and Microsoft certificates and was working as a tutor in an educational center for a couple of years. In his free time he tweets, writes posts for his website, participates in OpenSource projects, goes in for sports and plays music.

GlennG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week