This tutorial is out of date and no longer maintained.
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.
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.
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.
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 applications 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.
In case you are wondering if it is possible to delete an 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 to 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.
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.
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.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!