Easily Allow File Uploads with Rails and Refile
Enabling image uploading as a feature in your web application cannot be underestimated. Especially when it is an application that has to do with users having a profile. There are lots of gems available out there to help you handle this in a few lines of code, and most of them have been covered here on SitePoint Ruby.
They include:
- CarrierWave – Image Processing with Rails by Vasu K.
- PaperClip – Uploading Files with PaperClip by Ilya Bodrov-Krukowski
- Dragonfly – Better Uploads with Dragonfly by Ilya Bodrov-Krukowski
In this tutorial, we will be learning how to enable image uploading in Rails using Refile. As written on Refile’s github page, it is a modern file upload library for Ruby applications. It is simple, yet powerful.
For the curious ones reading this you might want to know what makes Refile better….did I just say it is? Anyway, that is left for you to decide. :)
Refile features includes:
- Configurable backends, file system, S3 etc.
- Convenient integration with ORMs.
- On fly manipulation of images and other files.
- Streaming IO for fast and memory friendly uploads.
- Works across form redisplays, i.e, when validations fail, even on S3.
- Effortless direct uploads, even to S3.
- Support for multiple uploads.
So what do you think? Do you want to give it a shot?
if "answer" = "yes"
hop_on_board_with_me
else
still_tag_along
end
Let’s roll!
Rails Application Setup
We are going to craft an app to basically save the images of our family members and friends. We will be the only user of this app, so it will be as simple as possible.
Create a new Rails app called photobook
:
rails new photobook
Let us generate a scaffolding for our application:
rails g scaffold Photo name:string
Next we apply our migration:
rake db:migrate
Integrating Refile
Open your Gemfile
and add the gem:
#Gemfile
gem 'refile', require: 'refile/rails'
gem 'refile-mini_magick'
Install the gems you just added:
bundle install
The refile-mini_magick
is for image processing. You need to have ImageMagick installed on your system.
Depending on your system, you can install ImageMagick following this:
brew install imagemagick # OS X
sudo apt-get install imagemagick # Ubuntu
After doing that, add the attachment method to use Refile in your model:
#app/models/photo.rb
class Photo < ActiveRecord::Base
attachment :profile_image
end
Let’s generate a new migration to add a new column for profile_image
to our photos
table:
rails generate migration add_profile_image_to_photos profile_image_id:string
rake db:migrate
We need to set up strong parameters to allow profile_image
in our photos_controller
:
#app/controllers/photos_controller.rb
def photo_params
params.require(:photo).permit(:name, :profile_image)
end
Navigate to your views to add the attachment field in your form:
#app/views/_form.html.erb
<%= form_for(@photo) 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 |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div>
<%= f.attachment_field :profile_image %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
To show the file in your views, edit your index and show page to look like this:
#app/views/index.html.erb
<p id="notice"><%= notice %></p>
<h1>Listing Photos</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @photos.each do |photo| %>
<tr>
<td><%= photo.name %></td>
<td><%= image_tag(attachment_url(photo, :profile_image, :fill, 100, 100)) %></td>
<td><%= link_to 'Show', photo %></td>
<td><%= link_to 'Edit', edit_photo_path(photo) %></td>
<td><%= link_to 'Destroy', photo, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Photo', new_photo_path %>
#app/views/show.html.erb
<p id="notice"><%= notice %></p>
<p>
<strong>Name:</strong>
<%= @photo.name %>
<%= image_tag attachment_url(@photo, :profile_image, :fill, 100, 100, format: "jpg") %>
</p>
<%= link_to 'Edit', edit_photo_path(@photo) %> |
<%= link_to 'Back', photos_path %>
Removing Attachments
You may want to remove an old image and add a new one. Refile has a functionality to help in this scenario. It adds an attribute to your model when you use the attachment
method, which is designed to be used with a checkbox in a form.
Here is how to do it:
#app/views/photo/_form.html.erb
<%= form_for(@photo) 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 |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div>
<%= f.label :profile_image %>
<%= f.attachment_field :profile_image %>
<% if @photo.profile_image_id? %>
<%= image_tag attachment_url(@photo, :profile_image, :fill, 100, 100, format: "jpg") %>
<%= f.checkbox :remove_profile_image %>
<%= f.label :remove_profile_image %>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
In the above code we use a checkbox to select the option of removing the image. We do not want the checkbox showing when no image has been uploaded so we use an if
statement to check for there is a profile_image_id
. If there is, the profile image and the option to remove it are displayed. This gives our application a professional look. :)
For the remove option to work, we need to whilelist :remove_profile_image
in strong params. So navigate to your photos_controller
and add :remove_profile_image
.
Here is how your controller should look:
#app/controllers/photos_controller.rb
class PhotosController < ApplicationController
before_action :set_photo, only: [:show, :edit, :update, :destroy]
# GET /photos
# GET /photos.json
def index
@photos = Photo.all
end
# GET /photos/1
# GET /photos/1.json
def show
end
# GET /photos/new
def new
@photo = Photo.new
end
# GET /photos/1/edit
def edit
end
# POST /photos
# POST /photos.json
def create
@photo = Photo.new(photo_params)
respond_to do |format|
if @photo.save
format.html { redirect_to @photo, notice: 'Photo was successfully created.' }
format.json { render :show, status: :created, location: @photo }
else
format.html { render :new }
format.json { render json: @photo.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /photos/1
# PATCH/PUT /photos/1.json
def update
respond_to do |format|
if @photo.update(photo_params)
format.html { redirect_to @photo, notice: 'Photo was successfully updated.' }
format.json { render :show, status: :ok, location: @photo }
else
format.html { render :edit }
format.json { render json: @photo.errors, status: :unprocessable_entity }
end
end
end
# DELETE /photos/1
# DELETE /photos/1.json
def destroy
@photo.destroy
respond_to do |format|
format.html { redirect_to photos_url, notice: 'Photo was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_photo
@photo = Photo.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def photo_params
params.require(:photo).permit(:name, :profile_image, :remove_profile_image)
end
end
File Type Validations
With Refile you can restrict the file extensions and content type users are allowed to upload. This is more of a convenient feature for your users, and not a security feature. With this feature you can warn users if they try to upload an invalid file.
To enable this, open your model and add edit the attachment
method line:
#app/models/photo.rb
attachment :profile_image, content_type: "image/jpeg"
That will restrict the allowed files to images with a JPEG extension. If you want to use a combination of different extension, it looks like this:
#app/models/photo.rb
attachment :profile_image, content_type: ["image/jpeg", "image/png", "image/gif"]
Here is a better way of doing this, especially when it has to do with images:
#app/models/photo.rb
attachment :profile_image, type: image
That will work!
Conclusion
Now you know how to enable image uploading in your Rails application using Refile. You might want to give this a shot in your next project. Check out Refile’s documentation to see how you can add other exciting capabilities Refile has to offer. Have a great time learning!