Introduction

Quite often in our projects we need to allow users to upload files. CarrierWave makes it easy for us to allow this.

Rails Version 4.0+
Gems carrierwave 0.9+
boostrap-sass 2.3.2

Let's walk through a simple example app. Lets say we are building a simple application that allows an HR person to upload and track resumes. We will need a few things to make this work. First, we will need a Resume model. The resume model will help us track each resume, including the name and the attachment itself. The next thing we will need is a resumes controller. The resumes controller allows us to see our resumes, upload new ones, and delete existing ones. Finally, we will need an uploader. Uploaders are small tidbits of code that tell CarrierWave how to handle the uploaded files. Alright, let's get to it!

Rails Application Setup

After creating a new project, open up your gemfile and add the following 2 gems. The first is CarrierWave itself, and the second is the bootstrap-sass gem, which we will use to style the application. You can optionally use your own styles instead.

Gemfile:

gem 'carrierwave', '~> 0.9'
gem 'bootstrap-sass', '~> 2.3.2'

Next, lets perform a bundle install and then create our models and controllers that we will need for this application. Run the following commands from the terminal/console window. The name field is the owner's name and the attachment field will store the attachment information for CarrierWave.

Terminal Commands

bundle install
rails g model Resume name:string attachment:string
rake db:migrate
rails g controller Resumes index new create destroy

Great! Now we have the basic structure set up. Now we need to create an uploader. As mentioned earlier, uploaders tell carrierwave how to handle the file once it's uploaded. Uploaders can be customized to include almost any type of functionality you can think of. Run the commands below to create the uploader.

Terminal Commands

rails g uploader attachment

Now lets tell rails that we want to use the uploader we created in our resume model. Open the resume model and add the changes listed below.

app/models/resume.rb:

class Resume < ActiveRecord::Base
  mount_uploader :attachment, AttachmentUploader # Tells rails to use this uploader for this model.
  validates :name, presence: true # Make sure the owner's name is present.
end

Before we work on our controller, lets modify our routes file. We need to modify our routes file to use the resources keyword rather than get. Open your routes file and modify it to look like thing following code (Replace CarrierWaveExample with your existing app name.)

config/routes.rb

CarrierWaveExample::Application.routes.draw do
  resources :resumes, only: [:index, :new, :create, :destroy]
  root "resumes#index"
end

Now lets edit our resumes controller and add some code to handle form processing. Open your resumes controller and change it so that it matches the code below.

app/controllers/resumes_controller.rb:

class ResumesController < ApplicationController
  def index
    @resumes = Resume.all
  end

  def new
    @resume = Resume.new
  end

  def create
    @resume = Resume.new(resume_params)

    if @resume.save
      redirect_to resumes_path, notice: "The resume [email protected]} has been uploaded."
    else
      render "new"
    end
  end

  def destroy
    @resume = Resume.find(params[:id])
    @resume.destroy
    redirect_to resumes_path, notice:  "The resume [email protected]} has been deleted."
  end

private
  def resume_params
    params.require(:resume).permit(:name, :attachment)
  end
end

Next, lets style our app a bit. Open up the resumes.css.scss file and add the following line to the end of the file.

app/assets/stylesheets/resumes.css.scss:

@import "bootstrap";

Next, open up your applications layout and modify it so it looks like the code listed below.

app/views/layouts/application.html.erb:

<!DOCTYPE html>
<html>
<head>
  <title>CarrierWaveExample</title>
  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body>
<div class="container" style="padding-top:20px;">
<%= yield %>
</div>
</body>
</html>

Now we need to set up our views. First lets do the index view. Modify the index.html.erb file to match what is listed below:

app/views/resumes/index.html.erb:

<% if !flash[:notice].blank? %>
  <div class="alert alert-info">
  <%= flash[:notice] %>
  </div>
<% end %>
<br />
<%= link_to "New Resume", new_resume_path, class: "btn btn-primary" %>
<br />
<br />
<table class="table table-bordered table-striped">
  <thead>
    <tr>
      <th>Name</th>
      <th>Download Link</th>
      <th>&nbsp;</th>
    </tr>
  </thead>
  <tbody>
    <% @resumes.each do |resume| %>
      <tr>
        <td><%= resume.name %></td>
        <td><%= link_to "Download Resume", resume.attachment_url %></td>
        <td><%= button_to "Delete",  resume, method: :delete, class: "btn btn-danger", confirm: "Are you sure that you wish to delete #{resume.name}?" %></td>
      </tr>
    <% end %>
  </tbody>
</table>

Now, lets edit new.html.erb and add our form code. Modify new.html.erb so that it looks like the code listed below.

app/views/resumes/new.html.erb:

<% if [email protected]? %>
  <div class="alert alert-error">
    <ul>
      <% @resume.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>

<div class="well">
  <%= form_for @resume, html: { multipart: true } do |f| %>
    <%= f.label :name %>
    <%= f.text_field :name %>
    <%= f.label :attachment %>
    <%= f.file_field :attachment %>
    <%= f.submit "Save", class: "btn btn-primary" %>
  <% end %>
</div>

Phew! Now if you start a rails server and visit http://localhost:3000 you'll be able to upload new resumes. Click 'New Resume', enter a name, click browse, find a file, and click save. Your file should upload and the download link on the index page should allow you to download that file.

Screenshot:
screenshot

Almost done! One last thing we need to do is filter the list of allowed filetypes. Right now any type of file can be uploaded, so lets constrain the list down to a couple of different file types. Open up the attachment uploader and modify it so that it looks like the code below:

app/uploaders/attachment_uploader.rb

class AttachmentUploader < CarrierWave::Uploader::Base
  storage :file

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

  def extension_white_list
    %w(pdf doc htm html docx)
  end
end

Now when you attempt to upload a new attachment with an invalid file type you will get a validation message. That's it! That is all there is to it!