[ROR] File uploading with refile

With RoR geeks, we're so familiar with two famous gems Carrierwave and Paperclip when implementing file uploader for RoR project. In this post, I want to introduct an alternative for these big names, Refile.

Who are you, Refile?

Refile, an energetic guy came from Elabs and we can easily find him on RubyGems and on Github. Although appearing just less than 1 year, he has gave strong impressions by his abilities, comparing with two old man Carrierwave and Paperclip.

Basically, he provides:

  • Backends: cache and persist files
  • Model attachments: map files to model columns
  • A Rack application: streams files and accepts uploads
  • Rails helpers: conveniently generate markup in your views
  • A JavaScript library: facilitates direct uploads

To understand a men, there're no any better ways than directly working with him so I created a small RoR project in my Gibhub to know about his interesting guy.

How can I cooperate with you?

The first thing we need to do to invite him to a RoR project is add his name and assistant to Gemfile

gem "refile", require: "refile/rails"
gem "refile-mini_magick"

These gems require another library named ImageMagick but I think it doesn't matter with Rubyer.

Model

Similar to senior File Uploader, Refile can interact directly with Database though models. However, he has some differences which make him became more mordern

Defination

In my personal project, I have many Collections and each of them contain many UploadedFiles and the main role of Refile in here is manage UploadedFile through attach_file.

class UploadedFile < ActiveRecord::Base
  belongs_to :collection
  attachment :attach_file
end
class Collection < ActiveRecord::Base
  has_many :uploaded_files, dependent: :destroy
  accepts_nested_attributes_for :uploaded_files, reject_if: :empty_attach

  def empty_attach(attrs)
    attrs[:attach_file].blank? && attrs[:id].blank?
  end
end

And of course, I have to provide for him a place to store the attach files

$ rails generate migration add_attach_file_to_uploaded_files attach_file_id:string
$ rake db:migrate

Should be remember that the column we create on database must be different with model (attach_file in model and attach_file_id in database).

Metadata

When a new file is uploaded to server, it's true that our main purpose is storing file, however, we wanna to know more about file properties including file extensions, the size or just simple like file name. With Carrierwave and Paperclip, we have to define functions in model that are executed before the information save to database. Reducing all these messy stuff, Refile execute it in default case. We just add new columns having name that match with Refile function and when saving new file, everything will be done automatically.

$ rails generate migration add_metadata_to_uploaded_files
class AddMetadataToUploadedFiles < ActiveRecord::Migration
  def change
    add_column :uploaded_files, :attach_file_filename, :string
    add_column :uploaded_files, :attach_file_size, :integer
    add_column :uploaded_files, :attach_file_content_type, :string
  end
end

Then now, I don't have to spend many hours to track data and write so many functions to retrieve/keep the file properties.

File extensions validation

I want my collection contain only images because I wanna to show off to my friends but what'll happens if I accidentally upload a pdf file? It's to inconvenience if users have to download it for preview in just few seconds. So Refile help me to limit the file extensions in UploadedFile model by

  attachment :attach_file, content_type: ["image/jpeg", "image/png", "image/gif"]

or more simple

  attachment :profile_image, type: :image

ReFile has his own definition about file content in Refile::Type class so if I want to make another collecion containing Microsoft Word files, I can easily add it to Collections

Front End

It's undoubted that Rails has been changed significantly in some latest version but the helper functions're still so boring. Containing many dangerous weapons, Refile will make File Uploader more interesting than the Rails way.

Ruby helpers

In view, we need to present basic infomration of previous files as well as ability to modify and create the files. In this part, I will show some essential functions of Refile that added to Rails helpers to easily to interact.

Basic form
   = form_for @collection do |f|
      = f.fields_for :uploaded_files do |form|
        = attachment_image_tag form.object, :attach_file, :fill, 150, 150
        ...
        = form.attachment_field :attach_file
        ...

We can see that attachment_image_tag and .attachment_field represent for two basic functions when working with file: storing and display. In this case, I just specific for image, with another file types, we can use attachment_url instead of attachment_image_tag. All these functions are made for Refile

Multiple files upload

Previously, to upload many files in Rails, the developers have to use a complicated form using nested attributes method. And of course, the controllers and models isn't allowed to unfollow that track. Refile brings to us another deadly tool that strongly support multiple files uploading from view helper to model. Let see how clean that Refile made from view to controler and models

    ...
    = form_for @collection do |f|
      = f.label :uploaded_files
      = f.attachment_field :uploaded_files_attach_files , multiple: true
    ...
class CollectionsController < ApplicationController
  ...
  def update
    @collection.update collection_params
    redirect_to action: :show, id: @collection.id
  end
  ...
  def collection_params
    params.require(:collection).permit(uploaded_files_attach_files: [])
  end
end
class UploadedFile < ActiveRecord::Base
  belongs_to :collection
  attachment :attach_file
end
class Collection < ActiveRecord::Base
  has_many :uploaded_files, dependent: :destroy
  accepts_attachments_for :uploaded_files, attachment: :attach_file, append: true
end

As mentioned before, single Collection can has many UploadedFile which has attach_file column to store the file. Now, the long list attribute in collection_params shorten to just empty array and if we want new files can be save without deleting previous ones, enable append: true. It's so easy as apple pie.

Fetching from remote URL

We are living in Internet and everything we stored on cloud has its own URL. That's why Refile provide a method to retrieve and store file though the URL. Just adding a few lines code to view and controller and it's done

    ...
    = form.label :remote_attach_file_url, "Or specify URL"
    = form.text_field :remote_attach_file_url
    ...
class CollectionsController < ApplicationController
   def collection_params
    params.require(:collection).permit(uploaded_files_attributes: [ :attach_file, :remote_attach_file_url])
  end
end
Remove attach file

Basically, we always create a record for storing file and it will be deleted if we remove that file but in some specific cases, the information in that record is so important with us. In this circumstance, using removal file function of Refile is the smart choice. Firstly, we need a checkfor to mark the file need to erase in view

  ...
  = form.label :remove_attach_file
  ...

Next, make the controller to recognize this attribute

class CollectionsController < ApplicationController
   def collection_params
    params.require(:collection).permit(uploaded_files_attributes: [... , remove_attach_file])
  end
end

Refile will automatically detect the removal attributes then remove just attach_file without impact to other fields of record.

Javascript intergration

Sometimes, the process of uploading file takes more time than in the blink of an eyen due to poor connection or infrastructure issue. We need something to notice user about this. Althoug still young, Refile is an meticulous man and he give us some event about file processing

form.addEventListener("upload:start", function() {
  // ...
});

form.addEventListener("upload:success", function() {
  // ...
});

input.addEventListener("upload:progress", function() {
  // ...
});

This extra JS functions is not only to satisfy the users but also help the developers to check the properness of system

You're a prospective guy!

Which is better, Refile, Carrierwave or Paperclip? There's no exact answer but 2 weeks working with Refile has given many impressions and I definitly let a reservation for him in Gemfile in next projects.

In this article, I don't mention about another special function of Refile, intergration with large scale storing system like Amazon S3 and I hope that in near future, there will be a post talking about this.

Essential links