Using Paperclip with Amazon S3

I recently launched the website for my wedding on Heroku. I opted to use Thoughtbot's popular Paperclip gem to manage photo attachments for the site. It's a snap to use with filesystem storage (the default), but Heroku doesn't allow applications to have access to the filesystem. Plan B then is to use Paperclip's ability to manage files over Amazon's Simple Storage Service (aka Amazon S3). S3 is cloud storage for ground people. It offers super cheap storage and huge bandwidth, allowing small websites to use it as a simple CDN. Initial setup is simple, albeit a bit unusual.

Basic S3 Setup

After registering for the service at Amazon's site, you'll need to collect your access credentials to allow Rails to connect to your account. You can find this in the Security Credentials link under the Your Account menu. Create a simple yaml file in your config directory with the following details:

# config/s3.yml
access_key_id: <your access key here>
secret_code: <your secret code here>

Note that this file can be configured to support multiple environments as well:

# config/s3.yml
development:
  access_key_id: <your development access key here>
  secret_code: <your development secret code here>
production:
  access_key_id: <your production access key here>
  secret_code: <your production secret code here>

S3 Buckets

I found that the tricky part in setting up Paperclip was dealing with S3 buckets. Buckets are something like file folders in the cloud, with the unusual stipulation that each bucket name must be unique across the entire service. Paperclip requires that you specify an S3 bucket to store your files in; unfortunately it doesn't handle the creation of the buckets for you. That's where the aws-s3 gem comes into play. Install it with gem install aws-s3, drop into the Rails console and execute the following:

# load the aws-s3 gem
>> require 'aws/s3'
=> []
# connect to your account
>> AWS::S3::Base.establish_connection!(YAML.load_file('config/s3.yml').symbolize_keys!)
=> <AWS::S3::Connection:0x2236d90 @secret_access_key=<your secret key>, @http=#<Net::HTTP s3.amazonaws.com:80 open=false>, @access_key_id=<your access id>, @options={:access_key_id=><your access id>, :secret_access_key=><your secret key>, :server=>"s3.amazonaws.com", :port=>80}>
# create your unique bucket
>> AWS::S3::Bucket.create('your_unique_s3_bucket')
=> true

Paperclip with S3

Once you've got your configuration file set and bucket created, it's time to set up your model. Paperclip's RDOC is really great at describing all the various options for their model and is well worth the time to read through. On top of that, there are several great tutorials on getting Paperclip running. The basics though, with the important S3 bits added, look like this:

# models/photo.rb
has_attached_file :image,
  :styles => { :thumbnail => "100x100>" },
  :storage => :s3,
  :s3_credentials => "#{Rails.root}/config/s3.yml",
  :bucket => "your_unique_s3_bucket";

Note that you can specify the :bucket option in your config/s3.yml file instead.

Using ImageMagick to Auto-Orient Images

With the above, you'll have a functional S3-backed Paperclip-powered model. I quickly ran into an annoying problem though. Modern digital cameras add special metadata to their photos that indicates the orientation of the camera when a photo is taken. Unfortunately, most web browsers aren't smart enough yet to handle that extra info, so whereas the photo appears as intended on the desktop, it appears flopped onto its side on the web. Some developers have taken to using RMagick to properly orient their photo uploads, but I've found that there's a simpler way. Paperclip exposes a hook to send custom conversion flags straight to ImageMagick when a photo is uploaded. Even better, ImageMagick has a simple flag -auto-orient to handle automatically orient images according to their metadata. Simply change the model above to:

# models/photo.rb
has_attached_file :image,
  :styles => { :thumbnail => "100x100>" },
  :storage => :s3,
  :s3_credentials => "#{Rails.root}/config/s3.yml",
  :bucket => "your_unique_s3_bucket",
  :convert_options => { :all => "-auto-orient" }

You'll now have a modern-camera-friendly, cloud-hosted image attachment system for your app. Enjoy!

blog comments powered by Disqus