Easily Allow File Uploads with Rails and Refile

Share this article

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:

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 SilasKingsley Silas
View Author

Kingsley Silas is a web developer from Nigeria. He has a hunger for acquiring new knowledge in every aspect he finds interesting.

file uploadGlennGRuby on Rails
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week