[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 ...
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 uploadPreviously, 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 URLWe 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 endRemove 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
- Github: https://github.com/refile/refile
- API: http://www.rubydoc.info/gems/refile
- Example: https://github.com/refile/refile_example_app
- Tutorial from GoRails: https://gorails.com/episodes/file-uploads-with-refile