E’ oramai una consuetudine nelle moderne applicazioni web fornire un un sistema di upload di immagini usando il drag & drop. Partendo da un caso reale che ho sviluppato, vi spiego come farlo in Ruby on Rails.

In questo piccolo tutorial faremo uso di Fine Uploader, una libreria Javascript che si occupa della gestione del drag & drop sulla pagina web. La libreria è alla versione 3.3 ed è, a mio parere, davvero molto ben sviluppata. Si può utilizzare in unione a jQuery oppure semplicemente usando le direttive native Javascript. Tra le caratteristiche principali, vi segnalo queste:
- Compatibile con tutti i browser moderni e con IE dalla versione 7 in avanti. Per Internet Explorer chiaramente c’è un fallback su iframe, mentre per gli altri browser l’implementazione avviene tramite XMLHttpRequest che consente l’upload multiplo in AJAX con progress bar.
- Non fa uso di Flash e jQuery è opzionale
- Upload immediati o tramite accodamento
- Integrazione con Twitter Bootstrap
Fine Uploader è fornito anche tramite una gemma che andremo ad aggiungere al nostro progetto.
Per gestire l’effettivo upload serverside, faremo uso di Carrierwave e di una versione modificata della classe StringIO per l’upload di file da stringhe.
Il progetto che ho sviluppato fa uso di Twitter Bootstrap quindi troverete nel codice alcune classi CSS specifiche di questo framework.

Procediamo con dettagliare i pezzi di codice necessari. Iniziamo con il Gemfile:
gem "carrierwave" gem "mini_magick" gem "fine-uploader-rails"
Andiamo a creare questo file in config/initializers/stringiohax.rb, come descritto dal wiki di Carrierwave:
class AppSpecificStringIO < StringIO attr_accessor :filepath def initialize(*args) super(*args[1..-1]) @filepath = args[0] end def original_filename File.basename(filepath) end end
Includiamo i javascript e i css necessari per attivare Fine Uploader nel nostro progetto. Come descritto dalla pagina GitHub della gemma, includiamo la libreria javascript nel nostro application.js:
//= require fineuploader.jquery
e aggiungiamo anche il foglio di stile nel nostro CSS (application.css):
*= require fineuploader
Modello e controller Image
E’ stato creato un modello Image che detiene tutte le informazioni sull’immagine caricata. Il modello creato fa uso di un uploader, attachmentUploader, creato tramite il generatore di Carrierwave e poi modificato aggiungendo la creazione di un thumbnail chiamato :thumb, di cui faremo uso nel controller:
rails generate uploader Attachment
Il modello risultante è il seguente:
class Image < ActiveRecord::Base attr_accessible :name mount_uploader :attachment, AttachmentUploader end
che fa riferimento a questa tabella nel database:
class CreateImages < ActiveRecord::Migration def change create_table :images, :force => true do |t| t.string :title t.string :attachment t.integer :user_id t.timestamps end end end
Il controller gestisce due azioni: la creazione (upload) dell’immagine e la sua eliminazione. L’unica particolarità è l’uso di AppSpecificStringIO nel metodo create. Questo è il codice:
class ImagesController < ApplicationController respond_to :json, :html, :js protect_from_forgery :except => :create # POST /images # POST /images.json def create file = AppSpecificStringIO.new(params[:qqfile], request.raw_post) @image = Image.new() @image.attachment = file @image.user_id = current_user.id respond_to do |format| if @image.save format.html do render :text => {:success => true, :id => @image.id, :src => @image.attachment.url(:thumb)}.to_json, :layout => false end format.json do render json => {:success => true, :id => @image.id, :src => @image.attachment.url(:thumb)}, :layout => false end else format.html do render :text => @image.errors.to_json, :layout => false end format.json do render :text => @image.errors.to_json, :layout => false end end end end # DELETE /images/1 # DELETE /images/1.json def destroy authorize! :destroy, @user, :message => 'Non sei autorizzato.' @brand = Brand.find(params[:id]) @brand.destroy respond_to do |format| format.html { redirect_to user_path(current_user.id) } end end end
Viste e Javascript
La parte client-side richiede semplici modifiche. Nella vista (form.html.haml) aggiungiamo l’elemento che conterrà la drop-zone per il drag & drop:
#jquery-wrapped-fine-uploader
Nella stessa vista ci sarà anche la parte relativa alla modifica delle immagini già caricate. Nel nostro caso specifico, si fa uso di simple_form e le immagini sono delle risorse annidate (nested resource) di un oggetto coupon:
.row-fluid %ul.thumbnails#coupon_thumbnails = f.fields_for :coupons_images do |s| %li.span3 %a.thumbnail = image_tag s.object.image.attachment.url(:thumb)
Poi aggiungiamo in application.js questo metodo che si occupa di attivare Fine Uploader nella pagina:
$('#jquery-wrapped-fine-uploader').fineUploader({ request: { forceMultipart:false, endpoint: '/images/' }, text: { uploadButton: ' Carica una foto dal tuo computer', deleteButton: ' Elimina' }, classes: { success: 'alert alert-success', fail: 'alert alert-error' }, validation: { //allowedExtensions: ['jpeg', 'jpg', 'png'], //sizeLimit: 5351200 // 50 kB = 50 * 1024 bytes }, deleteFile: { enabled: true, endpoint: '/images/' } }).on('complete', function(event, id, fileName, responseJSON) { if (responseJSON.success) { hidden = '< input type="hidden" name="coupon[coupons_images_attributes][' + Math.floor((Math.random()*1000000)+1) + '][image_id]" value="' + responseJSON.id + '" / >'; $('#coupon_thumbnails').append('< li class="span3" >' + hidden + '< a class="thumbnail" >< img src="' + responseJSON.src + '" alt="' + fileName + '">< /a>< /li >'); } }).on('onSubmitDelete', function(event, id, fileName, responseJSON) { if (responseJSON.success) { } });
L’unica particolarità di cui tener conto è l’opzione forceMultipart:false
, indispensabile per far funzionare l’upload delle immagini via AJAX.
Conclusioni
Il sistema sviluppato si integra perfettamente con Bootstrap, creando un form drag & drop responsive e le opzioni messe a disposizione dalla libreria ci consentono di personalizzare completamente l’uploader.
Grazie a Rails e alla sua community ancora una volta siamo riusciti a creare un complesso sistema di caricamento multiplo delle immagini in breve tempo.