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!
Frequently Asked Questions (FAQs) about File Upload in Rails with Refile
How can I install Refile in my Rails application?
To install Refile in your Rails application, you need to add the gem to your Gemfile. Open your Gemfile and add the following line: gem 'refile', require: "refile/rails"
. After adding the gem, run bundle install
to install the gem in your application. Once installed, you can use Refile for file uploads in your Rails application.
How can I use Refile for file uploads in my Rails application?
To use Refile for file uploads, you need to add an attachment attribute to your model. For example, if you have a User model and you want to add an avatar, you would add attachment :avatar
to your User model. In your form, you can use the attachment_field
helper to create a file upload field.
How can I display uploaded files in my views?
To display uploaded files in your views, you can use the attachment_url
helper. This helper generates a URL for the uploaded file. You can use this URL in an image tag to display the uploaded image. For example, if you have a User model with an avatar, you can display the avatar with <%= image_tag attachment_url(@user, :avatar) %>
.
How can I validate file uploads with Refile?
Refile does not provide built-in validation for file uploads. However, you can use Rails’ built-in validation methods to validate file uploads. For example, you can use the validates_presence_of
method to ensure that a file is uploaded.
How can I handle multiple file uploads with Refile?
To handle multiple file uploads with Refile, you need to use an array of attachments. In your model, you would add attachment :images, multiple: true
. In your form, you can use the attachment_field
helper with the multiple: true
option to create a file upload field that allows multiple files.
How can I delete uploaded files with Refile?
To delete uploaded files with Refile, you can use the remove_<attachment>_id
attribute. This attribute is automatically added by Refile when you add an attachment attribute to your model. You can use this attribute in your form to create a checkbox that allows users to delete the uploaded file.
How can I store uploaded files with Refile?
By default, Refile stores uploaded files on the file system. However, you can configure Refile to store files in different backends. For example, you can configure Refile to store files on Amazon S3 by setting the Refile.store
and Refile.cache
to an instance of Refile::S3
.
How can I process uploaded files with Refile?
Refile allows you to process uploaded files using the attachment
method. This method accepts a block that is used to process the uploaded file. For example, you can use the attachment
method to resize an uploaded image.
How can I handle errors with Refile?
Refile does not provide built-in error handling. However, you can use Rails’ built-in error handling methods to handle errors. For example, you can use the validates_with
method to add a custom validator that handles errors.
How can I test file uploads with Refile?
To test file uploads with Refile, you can use Rails’ built-in testing methods. For example, you can use the fixture_file_upload
method to upload a fixture file in your tests.
Kingsley Silas is a web developer from Nigeria. He has a hunger for acquiring new knowledge in every aspect he finds interesting.