Top shelf web developer training.

Guided Paths

Follow our crafted routes to reach your goals.

Video Courses

Premium videos to build real apps.

Written Tutorials

Code to follow and learn from.

Find Your
Opportunity HIRED
Dismiss
Up

File Upload in Rails with PaperClip

Related Course

Get Started with JavaScript for Web Development

JavaScript is the language on fire. Build an app for any platform you want including website, server, mobile, and desktop.

Introduction

This is the first part of my series - covering file uploading gems available for Rails developers. For this edition, we will cover the popular Paperclip gem.

Paperclip is an easy to use file uploading tool made available by the awesome folks at thoughtbot.

In this tutorial, we will build a Rails application from scratch with file uploading functionality added. We will make use of Paperclip, seeing the awesome features it provides to us.

Here is a snapshot of what we want to build.

The code for this tutorial is available on Github.

Setting Up

For PaperClip to work, we need to have ImageMagick installed on our machine.

For Mac users you can do so by running the command.

brew install imagemagick

Ubuntu users should run the command.

sudo apt-get install imagemagick -y

Let us start by creating our Rails application. For the purpose of this tutorial I'll call mine ClipUploader.


rails new ClipUploader -T 

The -T flags tells Rails to generate the new application without the default test suite.

Once that is done, you will want to add Twitter Bootstrap for the purpose of styling your application.

Open your Gemfile and add the Bootstrap gem

#Gemfile

...
gem 'bootstrap-sass'

Run the command to install the gem.


bundle install

Rename app/assets/stylesheets/application.css to app/assets/stylesheets/application.scss and import the necessary files needed for Bootstrap to work.

#app/assets/stylesheets/application.scss

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

Using your text editor, create a new file to hold our navigation code.

#app/views/layouts/_navigation.html.erb

<nav class="navbar navbar-default">
    <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-collapse" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">ClipUploader</a>
    </div>
    <div class="collapse navbar-collapse" id="navbar-collapse">
        <ul class="nav navbar-nav navbar-right">
          <li><%= link_to 'Home', root_path %></li>
          <li><%= link_to 'Upload New Image', new_photo_path %></li>
        </ul>
    </div>
  </div>
</nav>

The code above creates a naviation bar to hold your main navigation links.

Tweak your application layout to look like what I have below:

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

<!DOCTYPE html>
<html>
  <head>
    <title>ClipUploader</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <!-- Renders navigation file located at app/views/layouts/_navigation.html.erb -->
    <%= render "layouts/navigation" %>
    <div id="flash">
      <% flash.each do |key, value| %>
        <div class="flash <%= key %>"><%= value %></div>
      <% end %>
    </div>
    <div class="container-fluid">
      <%= yield %>
    </div>
  </body>
</html>

To make the navigation bar visible in your application, you have to render it in your application layout. That is what we did above.

Integrate PaperClip

Open up your Gemfile and add the PaperClip gem.

#Gemfile

...
gem "paperclip", "~> 5.0.0"

Install the gem.

 bundle install 

We will be making use of one controller, let us generate that.

 rails generate controller Photos

Using your text editor, edit your PhotosController to look like what I have below:

 #app/controllers/photos_controller.rb

 class PhotosController < ApplicationController

 #Index action, photos gets listed in the order at which they were created
 def index
  @photos = Photo.order('created_at')
 end

 #New action for creating a new photo
 def new
  @photo = Photo.new
 end

 #Create action ensures that submitted photo gets created if it meets the requirements
 def create
  @photo = Photo.new(photo_params)
  if @photo.save
   flash[:notice] = "Successfully added new photo!"
   redirect_to root_path
  else
   flash[:alert] = "Error adding new photo!"
   render :new
  end
 end

 private

 #Permitted parameters when creating a photo. This is used for security reasons.
 def photo_params
  params.require(:photo).permit(:title, :image)
 end

end

In the above controller, we created three actions. The new and create actions are used when a photo is to be uploaded. If the photo gets saved the user is redirected to the root_path, else the new page is rendered.

Let us generate our Photo model.

rails generate model Photo title:string

Migrate your database:

rake db:migrate 

We need to add a few attributes to the photos table. To do that we will make use of the generator provided by Paperclip. Run the command below from your terminal.

rails generate paperclip photo image

This will generate a new migration file that looks like this:

#xxxxxxxx_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

Now run the migration.

rake db:migrate

Open up your Photo model to add PaperClip functionality.

#app/models/photo.rb

...
  #Mounts paperclip image
  has_attached_file :image

PaperClip cannot work if your Photo model is not equipped with it. You do so by adding the line of code used above.

You need to create the files below and paste in the code.

First, your index page should look like this.

#app/views/photos/index.html.erb

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

<div class="media">
  <% @photos.each do |photo| %>
    <div class="media-left">
      <!-- Renders image -->
      <%= 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>
  <% end %>
</div>

Your new.html.erb file should look like this.

#app/views/photos/new.html.erb

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

<%= form_for @photo, html: { multipart: true } do |f| %>
  <% if @photo.errors.any? %>
    <div id="error_explanation">
      <h2>
        <%= "#{pluralize(@photo.errors.count, "error")} prohibited this photo from being saved:" %>
      </h2>
      <ul>
        <% @photo.errors.full_messages.each do |msg| %>
          <li>
            <%= msg %>
          </li>
          <% end %>
      </ul>
    </div>
  <% end %>

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

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

  <div class="form-group">
        <%= f.submit 'Upload Photo', class: 'btn btn-default' %>
    </div>
<% end %>

Time to configure your routes.

Open your route configuration file and paste in the following.

#config/routes.rb

...
  root to: "photos#index"
  resources :photos

We are listing the available photos on the home page of our application. Fire up your rails server and point your browser to http://localhost:3000/photos/new and you should get this.

Validations

Try uploading an image and you will get an error that looks like this.

You get this because PaperClip is concerned about the security of your application against malicious files. To fix this open up your Photo model and add this line of code.

#app/models/photo.rb

...
  #This validates the type of file uploaded. According to this, only images are allowed.
  validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/

PaperClip provides you with validators to ensure that malicious files are not uploaded to your application - this is what you just did above. The method validates the file type. In the above code, only images are permitted to be uploaded.

Try uploading an image again and it will work this time.

#app/models/photo.rb

...
  #Use this if you do not want to validate the uploaded file type.
  do_not_validate_attachment_file_type :image

If you prefer to take care of the validation of files uploaded yourself. You can tell PaperClip explicitly not to validate your file type using the line of code above.

PaperClip provides more validation options for developers to protect their application against malicious file uploads. You have to add it to your model.

#app/models/photo.rb

...
  #Validates file, file type and file size
  validates_attachment :image, presence: true,
  content_type: { content_type: "image/jpeg" },
  size: { in: 0..10.kilobytes }

In the above code we want to validate that an image is uploaded, and also ensure that non-image files are not uploaded. We've also gone ahead to set the maximum size allowed to 10kilobytes.

Deleting a Photo

In case you are wondering if it is possible to delete a uploaded file, PaperClip has got you covered. Add the code below to your PhotosController.

#app/controllers/photos_controller.rb

...

  #Destroy action for deleting an already uploaded image
  def destroy
  @photo = Photo.find(params[:id])
    if @photo.destroy
      flash[:notice] = "Successfully deleted photo!"
      redirect_to root_path
    else
      flash[:alert] = "Error deleting photo!"
    end
  end

The destroy action is called on whenever a user wants to delete a photo. You'll need to add a button on your views. So open up app/views/photos/index.html.erb and make it look like this:

#app/views/photos/index.html.erb

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

<div class="media">
  <% @photos.each do |photo| %>
    <div class="media-left">
      <!-- Renders image -->
      <%= link_to image_tag(photo.image.url, class: 'media-object'), photo.image.url, target: '_blank' %>
      <!-- Button to delete image -->
      <%= link_to 'Remove', photo_path(photo), class: 'btn btn-danger', method: :delete, data: {confirm: 'Are you sure?'} %>
    </div>
    <div class="media-body">
      <h4 class="media-heading"><%= photo.title %></h4>
    </div>
  <% end %>
</div>

Now on your index page when you click the Remove button you will see a pop-up asking you to confirm if you want to delete the photo.

PaperClip VS Other Gems

You might be wondering, when is the best time for me to use PaperClip in my Rails application when compared to other gems. Here are the options I have for you.

  • Easy Attachment: PaperClip provides you with the option of enabling file upload in your Rails application easily. The process is involved is simple.
  • PaperClip is a great option when building simple application. It is advisable to avoid PaperClip is your application will be complex. For complex applications you may want to consider CarrierWave.
  • PaperClip is also not your perfect choice if your application will require multiple file upload. PaperClip handles single file upload pretty well.
  • If you need a gem that is actively maintained by an experienced team, PaperClip falls into such category. PaperClip is maintained and funded by thoughtbot.

Conclusion

We have been able to cover the basics and important aspects of file upload with PaperClip. To dig deep check out the official Github repo. In the next part of this series, we will take a look at another file uploading gem.