Carrierwave
A walkthrough of the Carrierwave Ruby gem to give applications the ability to upload images, files, and media.
Guide Tasks
  • Read Tutorial

What Carrierwave Provides

Uploading image, files, and various media can be challenging to build into an application from scratch. Carrierwave has been one of the more popular gems for managing uploading and as you'll see it provides a pretty powerful set of tools with a straightforward implementation. With Carrierwave you can:

  • Upload any type of file

  • Easily integrate with storage engines such as AWS's S3

  • Protect against malicious file uploads with a whitelisting capability

  • A DRY interface so that uploaders can be utilized from various models throughout an application

  • The ability to integrate outside libraries for features such as image manipulation (we'll do this in the walk through)

Drawbacks

File uploading is a complex feature to build into an application, so no gem that manages this process is going to be perfect. Some issues I've run into Carrierwave are:

  • It can be complex to integrate common UI elements such as progress bars for file uploads

  • Uploading video components that need to be converted to different codecs can be challenging

  • Uploading very large files requires quite a bit more work to prevent server timeouts (this is the case for most file uploading gems)

Implementation / Carrierwave Example

To get started let's create the application:

rails new carrierwave-tutorial -T

After running rake db:create && rake db:migrate we can start setting up the model that we'll be integrating the uploader with. Run the scaffold:

rails g scaffold Post title:string image:string description:text

After running rake db:migrate we can start integrating Carrierwave, begin with adding the following Gems into the Gemfile:

gem 'carrierwave'
gem 'mini_magick', '~> 3.5.0'
gem 'fog'
gem 'figaro'
gem 'unf'

Run bundle install and it will bring in all of the Gem code and we can run the generator that will create an uploader configuration file:

rails generate uploader Photo

This generator created a single file: app/uploaders/photo_uploader.rb, delete everything in the file and replace it with the contents below:

# app/uploaders/photo_uploader.rb
# encoding: utf-8

class PhotoUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  include Sprockets::Rails::Helper

  storage :fog

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  version :thumbnail do
   process :resize_to_fill => [20, 20]
  end

  version :profile_size do
   process :resize_to_fill => [300, 300]
  end

  def extension_white_list
   %w(jpg jpeg gif png)
  end
end

Going line by line let's see what this file makes available to us:

  1. It includes some helper methods and the MiniMagick module which gives us some nice image manipulation methods

  2. It specifies what storage engine to use, in this case we're going to be using fog and connect it to AWS for storing the uploading files

  3. The :thumbnail and :profile_size are methods that we can call when we want to render the images. This is handy since it means you can do something such as @post.image.thumbnail and it will automagically render an image that has been resized to 20x20 pixels, without having to manipulate the image sizes at the view layer

  4. The extension_white_list method lets you declare a list of file extensions that users are allowed to upload. For example, in this list users wouldn't be able to upload a PDF or .zip file

With out uploader configured we can now add the uploader to the model that we want to accept file uploads. I like this implementation since it is flexible enough for us to pick and choose which models should be able to take in a file. Update the post.rb file like I did below:

# app/models/post.rb

class Post < ActiveRecord::Base
  mount_uploader :image, PhotoUploader
end

The mount_uploader method takes in two arguments:

  • The attribute in the model that will store the image file URL

  • The uploader that we want to use. This is a flexible implementation since it means that you could have one model that is configured to work with a photo uploader, and another model that can interface with a video uploader

Now we need to integrate the API keys for the AWS connection and we're going to use the figaro gem to securely manage those credentials. Running:

figaro install

Will create the config/application.yml file and also add the file to the .gitignore file. It's very important that you never push these credentials to your version control system (aka GitHub/Bitbucket) since hackers could take these credentials and use them to do things such as spinning up new servers. I had a developer who worked for me who accidentally pushed up my AWS credentials and within 10 hours a hacker had scraped the keys and ran up over $20,000 in charges (AWS thankfully caught the hack and didn't charge me for it).

Place the following code into the config/application.yml file (with your details filled in obviously):

AWS_ACCESS_KEY_ID: "Your AWS access key"
AWS_SECRET_ACCESS_KEY: "Your AWS secret access key"
development:
  AWS_BUCKET: "Your S3 development bucket name"
production:
  AWS_BUCKET: "Your S3 production bucket name"

Now to create the connection to AWS, create a new initializer for the fog gem:

# config/initializers/fog.rb

CarrierWave.configure do |config|
  config.fog_credentials = {
    :provider               => 'AWS',
    :aws_access_key_id      => ENV['AWS_ACCESS_KEY_ID'],
    :aws_secret_access_key  => ENV['AWS_SECRET_ACCESS_KEY']
  }
  config.fog_directory  = ENV['AWS_BUCKET']
  config.fog_public     = false
end

This initializer file will make the connection to the AWS API and securely pass in the parameters we need for the file uploading to work.

Now that our setup is complete let's update the file uploader in the view:

<!-- app/views/posts/_form.html.erb -->

<%= form_for(@post) do |f| %>
  <% if @post.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>

      <ul>
      <% @post.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :image %><br>
    <%= f.file_field :image %>
  </div>
  <div class="field">
    <%= f.label :description %><br>
    <%= f.text_area :description %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Let's startup the Rails server and test this out, navigate to localhost:3000/posts/new and fill out the fields, including uploading a file. If everything is setup properly everything should flow through and the file should be uploaded to S3, the show page after the upload should look something like below:

large

That URL is the image file page coming from S3 where the image is now stored. To see what this looks like update the view template like below and use the image_tag view helper:

<!-- app/views/posts/show.html.erb -->

<p id="notice"><%= notice %></p>

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<p>
  <strong>Image:</strong>
  <%= image_tag(@post.image.profile_size.url) %>
</p>

<p>
  <strong>Description:</strong>
  <%= @post.description %>
</p>

<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>

Notice how I called the profile_size method on the image so it automatically sizes it with the same parameters we used in the uploader configuration file.

And that's how easy it is to build a file uploader into an application with Carrierwave.

Code

Resources