Harlow Ward

This article was contributed by Harlow Ward

Harlow is a Ruby on Rails developer at HotelTonight. You can find him on twitter @futuresanta.

Uploading Files to S3 in Ruby with Paperclip

Last Updated: 01 February 2014

file uploads paperclip s3

Table of Contents

Many web apps require the user to upload images and other files for storage and processing. Paperclip is a cleanly abstracted Ruby library that reduces the complexity of file uploading and processing.

Using Paperclip with an external storage service such as Amazon S3 or Rackspace CloudFiles allows you to scale your application’s files and codebase independently. Such an architecture is required in highly elastic environments that distribute your application across multiple instances, such as Heroku.

This guide describes how to set up a Ruby on Rails application with image uploading via Paperclip and Amazon S3.

If you have questions about Ruby on Heroku, consider discussing it in the Ruby on Heroku forums.

Prerequisites

Note: Mac users can install ImageMagick with Homebrew brew install imagemagick. Windows users can use the Windows binary release.

Overview

Paperclip is an easy file attachment library for ActiveRecord. It treats files like model attributes. This means they aren’t saved to their final locations, nor are they deleted if set to nil, until ActiveRecord::Base#save is called. It can validate based on file size and presence. It can transform its assigned image into thumbnails if needed, and the only prerequisites are database columns and ImageMagick. Attached files are referenced in the browser by an understandable specification with sensible defaults.

Reference Application

Paperclip Demo Application

The reference application allows a user to manage a list of their friends.

  • Each friend will have an avatar with Paperclip providing the image upload and resizing functionality.
  • The app will demonstrate how to generate scaled down thumbnails, and display the resized images.
  • The application will also gracefully degrade to display a default image missing.png for friends without an avatar.

Download the source code from GitHub.

Configuration

Paperclip requires the following gems added to your Gemfile.

# Gemfile
gem 'paperclip'
gem 'aws-sdk'

Run bundle install and restart the Rails server after modifying the Gemfile.

We’ll also need to specify the AWS configuration variables for the production Environment.

# config/environments/production.rb
config.paperclip_defaults = {
  :storage => :s3,
  :s3_credentials => {
    :bucket => ENV['S3_BUCKET_NAME'],
    :access_key_id => ENV['AWS_ACCESS_KEY_ID'],
    :secret_access_key => ENV['AWS_SECRET_ACCESS_KEY']
  }
}

To test S3 uploads locally in development mode these settings must also be added to the development environment.

Additionally, we’ll need to the set the AWS configuration variables on the Heroku application.

$ heroku config:set S3_BUCKET_NAME=your_bucket_name
$ heroku config:set AWS_ACCESS_KEY_ID=your_access_key_id
$ heroku config:set AWS_SECRET_ACCESS_KEY=your_secret_access_key

International users (additional configuration)

If you are having issues uploading images please read the following two configuration sections.

If you continue to have issues please see the Paperclip documentation page for detailed configuration options.

To override the default URL structure and place the bucket’s name “domain-style” in the URL (e.g. your_bucket_name.s3.amazonaws.com). These options can be placed in the paperclip_defaults configuration hash shown above, or into an initializer.

# config/initializers/paperclip.rb
Paperclip::Attachment.default_options[:url] = ':s3_domain_url'
Paperclip::Attachment.default_options[:path] = '/:class/:attachment/:id_partition/:style/:filename'

If you are seeing the following error: “The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.” Try setting the specified endpoint with the s3_host_name config var.

# config/initializers/paperclip.rb
Paperclip::Attachment.default_options[:s3_host_name] = 's3-us-west-2.amazonaws.com'

Define the file attribute in the Model

To add attachment functionality to the Friend model use the Paperclip helper method has_attached_file and a symbol with the desired name of the attachment.

class Friend < ActiveRecord::Base
  # This method associates the attribute ":avatar" with a file attachment
  has_attached_file :avatar, styles: {
    thumb: '100x100>',
    square: '200x200#',
    medium: '300x300>'
  }

  # Validate the attached image is image/jpg, image/png, etc
  validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
end
Additional ImageMagick resizing options can be found here

The has_attached_file method also accepts a styles hash that specifies the resize dimensions of the uploaded image. The > and # symbols will tell ImageMagick how the image will be resized (the > will proportionally reduce the size of the image).

Update database

A database migration is needed to add the avatar attribute on Friend in the database schema. Run the following rails helper method to generate a stub migration.

$ rails g migration AddAvatarToFriends

Paperclip comes with the migration helper methods add_attachment and remove_attachment. They are used to create the columns needed to store image data in the database. Use them in the AddAvatarToFriends migration.

class AddAvatarToFriends < ActiveRecord::Migration
  def self.up
    add_attachment :friends, :avatar
  end

  def self.down
    remove_attachment :friends, :avatar
  end
end

This migration will create avatar_file_name, avatar_file_size, avatar_content_type, and avatar_updated_at attributes on the Friend model. These attributes will be set automatically when files are uploaded.

Run the migrations with rake db:migrate to update your database.

Upload form

Images are uploaded to your application before being stored in S3. This allows your models to perform validations and other processing before being sent to S3.

Form with File Input

Add a file input field to the web form that allows users to browse and select images from their local filesystem.

<div class="field">
  <%= f.label :avatar %><br />
  <%= f.file_field :avatar %>
</div>

When the form is submitted and the backing models are successfully persisted to the database, the file itself will be uploaded to S3.

Upload controller

With Rails 4 we’ll need to specify the permitted params. We’ll permit :name and :avatar in the params.

class FriendsController < ApplicationController
  # Other CRUD actions omitted

  def create
    @friend = Friend.new(friend_params)

    if @friend.save
      redirect_to @friend, notice: 'Friend was successfully created.' }
     else
       render action: 'new'
    end
  end

  private

  def friend_params
    params.require(:friend).permit(:avatar, :name)
  end
end

Large files uploads in single-threaded, non-evented environments (such as Rails) block your application’s web dynos and can cause request timeouts and H11, H12 errors. For files larger than 4mb the direct upload method should be used instead.

Image display

Files that have been uploaded with Paperclip are stored in S3. However, metadata such as the file’s name, location on S3, and last updated timestamp are all stored in the model’s table in the database.

Access the file’s url through the url method on the model’s file attribute (avatar in this example).

friend.avatar.url #=> http://your_bucket_name.s3.amazonaws.com/...

This url can be used directly in the view to display uploaded images.

<%= image_tag friend.avatar.url(:square) %>

The url method can take a style (defined earlier in the Friend model) to access a specific processed version of the file.

Paperclip Demo Application

As these images are served directly from S3 they don’t interfere with other requests and allow your page to load quicker than serving directly from your app.

Display the medium sized image by passing :medium to the url method.

<%= image_tag friend.avatar.url(:medium) %>

Deploy

Once you’ve updated your application to use Paperclip commit the modified files to git.

$ git commit -m "Upload friend images via Paperclip"

On deployment to Heroku you will need to migrate your database to support the required file columns.

$ git push heroku master
$ heroku run bundle exec rake db:migrate