Caricamento di file con Rails e Dragonfly

Qualche tempo fa ho scritto un articolo Uploading Files With Rails e Shrine che spiegava come introdurre una funzione di caricamento file nell'applicazione Rails con l'aiuto della gemma Shrine. Ci sono, tuttavia, un sacco di soluzioni simili disponibili, e uno dei miei preferiti è Dragonfly, una soluzione di caricamento di facile utilizzo per Rails and Rack creata da Mark Evans. 

Abbiamo parlato di questa biblioteca all'inizio dell'anno scorso ma, come con la maggior parte dei software, è utile dare un'occhiata alle biblioteche di volta in volta per vedere cosa è cambiato e come possiamo utilizzarlo nella nostra applicazione.

In questo articolo ti guiderò attraverso la configurazione di Dragonfly e spiegherò come utilizzare le sue funzionalità principali. Imparerai come:

  • Integrare Dragonfly nella tua applicazione
  • Configurare i modelli per lavorare con Dragonfly
  • Introdurre un meccanismo di caricamento di base
  • Introdurre convalide
  • Genera miniature di immagini
  • Esegui l'elaborazione dei file
  • Memorizza i metadati per i file caricati
  • Preparare un'applicazione per la distribuzione

Per rendere le cose più interessanti, creeremo una piccola applicazione musicale. Presenterà album e brani associati che possono essere gestiti e riprodotti sul sito web.

Il codice sorgente per questo articolo è disponibile su GitHub. È inoltre possibile controllare la demo di lavoro dell'applicazione.

Elenco e gestione degli album

Per iniziare, crea una nuova applicazione Rails senza la suite di test predefinita:

rota nuovo UploadingWithDragonfly -T

Per questo articolo userò Rails 5, ma la maggior parte dei concetti descritti si applica anche alle versioni precedenti.

Creazione del modello, del controller e delle rotte

Il nostro piccolo sito musicale conterrà due modelli: Album e Canzone. Per ora, creiamo il primo con i seguenti campi:

  • titolo (stringa): contiene il titolo dell'album
  • cantante (stringa) esecutore di album
  • image_uid (stringa) -un campo speciale per memorizzare l'immagine di anteprima dell'album. Questo campo può essere chiamato qualsiasi cosa tu voglia, ma deve contenere il _uid suffisso come indicato dalla documentazione di Dragonfly.

Crea e applica la migrazione corrispondente:

rails g model Titolo dell'album: string singer: string image_uid: string rails db: migrate

Ora creiamo un controller molto generico per gestire gli album con tutte le azioni predefinite:

albums_controller.rb

classe Albumsontroller < ApplicationController def index @albums = Album.all end def show @album = Album.find(params[:id]) end def new @album = Album.new end def create @album = Album.new(album_params) if @album.save flash[:success] = 'Album added!' redirect_to albums_path else render :new end end def edit @album = Album.find(params[:id]) end def update @album = Album.find(params[:id]) if @album.update_attributes(album_params) flash[:success] = 'Album updated!' redirect_to albums_path else render :edit end end def destroy @album = Album.find(params[:id]) @album.destroy flash[:success] = 'Album removed!' redirect_to albums_path end private def album_params params.require(:album).permit(:title, :singer) end end

Infine, aggiungi i percorsi:

config / routes.rb

risorse: album

Integrazione Dragonfly

È tempo che Dragonfly entri sotto i riflettori. Innanzitutto, aggiungi la gemma nel Gemfile:

Gemfile

gemma "libellula"

Correre:

bundle install rails genera libellule

L'ultimo comando creerà un inizializzatore chiamato dragonfly.rb con la configurazione di default. Lo metteremo da parte per ora, ma si può leggere su varie opzioni sul sito ufficiale di Dragonfly.

La prossima cosa importante da fare è equipaggiare il nostro modello con i metodi di Dragonfly. Questo viene fatto usando il dragonfly_accessor:

modelli / album.rb

dragonfly_accessor: image

Nota che qui sto dicendo :Immagine-si riferisce direttamente al image_uid colonna che abbiamo creato nella sezione precedente. Se tu, ad esempio, hai nominato la tua colonna photo_uid, poi il dragonfly_accessor il metodo dovrebbe ricevere :foto come argomento.

Se stai usando Rails 4 o 5, un altro passo importante è contrassegnare il :Immagine campo (non : image_uid!) come consentito nel controller:

albums_controller.rb

params.require (: album) .permit (: title,: singer,: image)

Questo è praticamente tutto - siamo pronti per creare viste e iniziare a caricare i nostri file!

Creazione di viste

Inizia con la vista indice:

views / album / index.html.erb

Album

<%= link_to 'Add', new_album_path %>
    <%= render @albums %>

Ora il parziale:

views / album / _album.html.erb

  • <%= image_tag(album.image.url, alt: album.title) if album.image_stored? %> <%= link_to album.title, album_path(album) %> di <%= album.singer %> | <%= link_to 'Edit', edit_album_path(album) %> | <%= link_to 'Remove', album_path(album), method: :delete, data: confirm: 'Are you sure?' %>
  • Ci sono due metodi Dragonfly da notare qui:

    • album.image.url restituisce il percorso dell'immagine.
    • album.image_stored? dice se il record ha un file caricato sul posto.

    Ora aggiungi le pagine nuove e modificate:

    views / album / new.html.erb

    Aggiungi album

    <%= render 'form' %>

    views / album / edit.html.erb

    modificare <%= @album.title %>

    <%= render 'form' %>

    views / album / _form.html.erb

    <%= form_for @album do |f| %> 
    <%= f.label :title %> <%= f.text_field :title %>
    <%= f.label :singer %> <%= f.text_field :singer %>
    <%= f.label :image %> <%= f.file_field :image %>
    <%= f.submit %> <% end %>

    La forma non è niente di speciale, ma ancora una volta si nota che stiamo dicendo :Immagine, non : image_uid, quando si esegue il rendering del file.

    Ora puoi avviare il server e testare la funzionalità di caricamento!

    Rimozione di immagini

    Quindi gli utenti sono in grado di creare e modificare album, ma c'è un problema: non hanno modo di rimuovere un'immagine, ma solo di sostituirla con un'altra. Fortunatamente, questo è molto facile da risolvere introducendo una casella di controllo "rimuovi immagine": 

    views / album / _form.html.erb

    <% if @album.image_thumb_stored? %> <%= image_tag(@album.image.url, alt: @album.title) %> <%= f.label :remove_image %> <%= f.check_box :remove_image %> <% end %>

    Se l'album ha un'immagine associata, la mostriamo e visualizziamo una casella di controllo. Se questa casella è impostata, l'immagine verrà rimossa. Nota che se il tuo campo è nominato photo_uid, allora il metodo corrispondente per rimuovere l'allegato sarà remove_photo. Semplice, non lo è?

    L'unica altra cosa da fare è permettere il remove_image attributo nel tuo controller:

    albums_controller.rb

    params.require (: album) .permit (: title,: singer,: image,: remove_image)

    Aggiunta di convalide

    A questo punto, tutto funziona perfettamente, ma non stiamo controllando l'input dell'utente, il che non è particolarmente eccezionale. Pertanto, aggiungiamo le convalide per il modello di album:

    modelli / album.rb

    valida: titolo, presenza: true valida: cantante, presenza: true convalida: immagine, presenza: true validates_property: width, of:: image, in: (0 ... 900)

    validates_property è il metodo Dragonfly che può controllare vari aspetti del tuo allegato: puoi convalidare l'estensione di un file, il tipo MIME, la dimensione, ecc..

    Ora creiamo un partial generico per rendere gli errori che sono stati trovati:

    views / shared / _errors.html.erb

    <% if object.errors.any? %> 

    Sono stati trovati i seguenti errori:

      <% object.errors.full_messages.each do |msg| %>
    • <%= msg %>
    • <% end %>
    <% end %>

    Impiega questo parziale all'interno del modulo:

    views / album / _form.html.erb

    <%= form_for @album do |f| %> <%= render 'shared/errors', object: @album %> <%#… %> <% end %>

    Modella i campi con errori un po 'per visualizzarli visivamente:

    fogli di stile / application.scss

    .field_with_errors display: inline; label color: red;  input background-color: lightpink; 

    Mantenere un'immagine tra le richieste

    Dopo aver introdotto le convalide, ci imbattiamo in un altro problema (uno scenario abbastanza tipico, eh?): Se l'utente ha commesso degli errori durante la compilazione del modulo, dovrà scegliere nuovamente il file dopo aver fatto clic sul Sottoscrivi pulsante.

    Dragonfly può aiutarti a risolvere anche questo problema usando a retained_ * campo nascosto:

    views / album / _form.html.erb

    <%= f.hidden_field :retained_image %>

    Non dimenticare di consentire anche questo campo:

    albums_controller.rb

    params.require (: album) .permit (: title,: singer,: image,: remove_image,: retained_image)

    Ora l'immagine verrà mantenuta tra le richieste! L'unico piccolo problema, tuttavia, è che l'input di caricamento del file continuerà a visualizzare il messaggio "scegli un file", ma questo può essere risolto con alcuni stili e un trattino di JavaScript.

    Elaborazione delle immagini

    Generazione di miniature

    Le immagini caricate dai nostri utenti possono avere dimensioni molto diverse, che possono (e probabilmente lo faranno) avere un impatto negativo sulla progettazione del sito web. Probabilmente vorresti ridimensionare le immagini ad alcune dimensioni fisse, e naturalmente ciò è possibile utilizzando il larghezza e altezza stili. Questo, tuttavia, non è un approccio ottimale: il browser dovrà comunque scaricare le immagini a dimensione intera e quindi ridurle.

    Un'altra opzione (che di solito è molto meglio) è generare anteprime di immagini con alcune dimensioni predefinite sul server. Questo è molto semplice da ottenere con Dragonfly:

    views / album / _album.html.erb

  • <%= image_tag(album.image.thumb('250x250#').url, alt: album.title) if album.image_stored? %> <%#… %>
  • 250x250 è, ovviamente, le dimensioni, mentre # è la geometria che significa "ridimensiona e ritaglia se necessario per mantenere le proporzioni con il centro di gravità". È possibile trovare informazioni su altre geometrie sul sito Web di Dragonfly.

    Il pollice il metodo è potenziato da ImageMagick, un'ottima soluzione per creare e manipolare le immagini. Pertanto, per vedere la demo di lavoro localmente, è necessario installare ImageMagick (sono supportate tutte le principali piattaforme). 

    Il supporto per ImageMagick è abilitato di default all'interno dell'inizializzatore di Dragonfly:

    config / inizializzatori / dragonfly.rb

    plugin: imagemagick

    Ora vengono generate le anteprime, ma non vengono memorizzate da nessuna parte. Ciò significa che ogni volta che un utente visita la pagina degli album, le anteprime verranno rigenerate. Ci sono due modi per superare questo problema: generarli dopo che il record è stato salvato o eseguendo la generazione al volo.

    La prima opzione prevede l'introduzione di una nuova colonna per memorizzare la miniatura e modificare la dragonfly_accessor metodo. Crea e applica una nuova migrazione:

    rails g migration add_image_thumb_uid_to_albums image_thumb_uid: string rails db: migrate

    Ora modifica il modello:

    modelli / album.rb

    dragonfly_accessor: image do copy_to (: image_thumb) | a | a.thumb ('250x250 #') end dragonfly_accessor: image_thumb

    Nota che ora è la prima chiamata a dragonfly_accessor invia un blocco che effettivamente genera la miniatura per noi e la copia nel image_thumb. Adesso usa solo il image_thumb metodo nelle tue opinioni:

    views / album / _album.html.erb

    <%= image_tag(album.image_thumb.url, alt: album.title) if album.image_thumb_stored? %>

    Questa soluzione è la più semplice, ma non è raccomandata dai documenti ufficiali e, quel che è peggio, al momento della scrittura non funziona con retained_ * i campi.

    Pertanto, lascia che ti mostri un'altra opzione: generare miniature al volo. Implica la creazione di un nuovo modello e la modifica del file di configurazione di Dragonfly. Innanzitutto, il modello:

    rails g model Thumb uid: string job: string rake db: migrate

    Il pollici la tabella ospiterà le miniature, ma verranno generate su richiesta. Per far sì che ciò accada, dobbiamo ridefinire il url metodo all'interno dell'inizializzatore Dragonfly:

    config / inizializzatori / dragonfly.rb

    Dragonfly.app.configure fa define_url do | app, job, opts | thumb = Thumb.find_by_job (job.signature) se thumb app.datastore.url_for (thumb.uid,: scheme => 'https') else app.server.url_for (lavoro) end end before_serve do | job, env | uid = job.store Thumb.create! (: uid => uid,: job => job.signature) end # ... end

    Ora aggiungi un nuovo album e visita la pagina principale. La prima volta che lo fai, il seguente output verrà stampato nei registri:

    DRAGONFLY: comando shell: "converti" "some_path / public / system / dragonfly / development / 2017/02/08 / 3z5p5nvbmx_Folder.jpg" "-resize" "250x250 ^^" "-gravity" "Center" "-crop" " 250x250 + 0 + 0 "" + repage "" some_path / 20170208-1692-1xrqzc9.jpg "

    Ciò significa che la miniatura viene generata per noi da ImageMagick. Tuttavia, se ricarichi la pagina, questa riga non verrà più visualizzata, il che significa che la miniatura è stata memorizzata nella cache! Puoi leggere un po 'di più su questa funzione sul sito Web di Dragonfly.

    Più elaborazione

    È possibile eseguire virtualmente qualsiasi manipolazione delle immagini dopo che sono state caricate. Questo può essere fatto all'interno del after_assign richiama. Per esempio, convertiamo tutte le nostre immagini in formato JPEG con una qualità del 90%: 

    dragonfly_accessor: image do after_assign | a | a.encode! ('jpg', '-quality 90') fine

    Esistono molte altre azioni che è possibile eseguire: ruotare e ritagliare le immagini, codificare con un formato diverso, scrivere testo su di esse, mescolare con altre immagini (ad esempio per posizionare una filigrana), ecc. Per vedere alcuni altri esempi, fare riferimento a la sezione ImageMagick sul sito Web di Dragonfly.

    Caricamento e gestione delle canzoni

    Naturalmente, la parte principale del nostro sito musicale sono le canzoni, quindi aggiungiamole ora. Ogni canzone ha un titolo e un file musicale e appartiene a un album:

    rails g model Album di canzoni: belongs_to title: string track_uid: string rails db: migrate

    Collega i metodi Dragonfly, come abbiamo fatto per il Album modello:

    modelli / song.rb

    dragonfly_accessor: track

    Non dimenticare di stabilire un ha molti relazione:

    modelli / album.rb

    has_many: canzoni, dipendenti:: destroy

    Aggiungi nuovi percorsi. Una canzone esiste sempre nell'ambito di un album, quindi renderò nidificati questi percorsi:

    config / routes.rb

    risorse: gli album fanno risorse: canzoni, solo: [: nuovo,: crea] fine

    Crea un controller molto semplice (ancora una volta, non dimenticare di consentire il traccia campo):

    songs_controller.rb

    class SongsController < ApplicationController def new @album = Album.find(params[:album_id]) @song = @album.songs.build end def create @album = Album.find(params[:album_id]) @song = @album.songs.build(song_params) if @song.save flash[:success] = "Song added!" redirect_to album_path(@album) else render :new end end private def song_params params.require(:song).permit(:title, :track) end end

    Mostra i brani e un link per aggiungerne uno nuovo:

    views / album / show.html.erb

    <%= @album.title %>

    di <%= @album.singer %>

    <%= link_to 'Add song', new_album_song_path(@album) %>
      <%= render @album.songs %>

    Codifica il modulo:

    views / canzoni / new.html.erb

    Aggiungi canzone a <%= @album.title %>

    <%= form_for [@album, @song] do |f| %>
    <%= f.label :title %> <%= f.text_field :title %>
    <%= f.label :track %> <%= f.file_field :track %>
    <%= f.submit %> <% end %>

    Infine, aggiungi il _canzone parziale:

    views / canzoni / _song.html.erb

  • <%= audio_tag song.track.url, controls: true %> <%= song.title %>
  • Qui sto usando l'HTML5 Audio tag, che non funzionerà con i browser più vecchi. Quindi, se stai mirando a supportare tali browser, usa un polyfill.

    Come vedi, l'intero processo è molto semplice. A Dragonfly non interessa davvero quale tipo di file desideri caricare; tutto ciò che devi fare è fornire un dragonfly_accessor metodo, aggiungere un campo appropriato, consentirlo e eseguire il rendering di un tag di input del file.

    Memorizzazione dei metadati

    Quando apro una playlist, mi aspetto di vedere alcune informazioni aggiuntive su ogni canzone, come la durata o il bitrate. Naturalmente, per impostazione predefinita queste informazioni non vengono memorizzate da nessuna parte, ma possiamo risolverlo facilmente. Dragonfly ci consente di fornire dati aggiuntivi su ogni file caricato e recuperarlo in seguito utilizzando il comando meta metodo.

    Le cose, tuttavia, sono un po 'più complesse quando lavoriamo con audio o video, perché per recuperare i loro metadati è necessaria una gemma speciale streamio-ffmpeg. Questo gioiello, a sua volta, si affida a FFmpeg, quindi per procedere è necessario installarlo sul PC.

    Inserisci streamio-ffmpeg nel Gemfile:

    Gemfile

    gemma 'streamio-ffmpeg'

    Installalo:

    installazione bundle

    Ora possiamo usare lo stesso after_assign callback già visto nelle sezioni precedenti:

    modelli / song.rb

    dragonfly_accessor: track do after_assign do | a | song = FFMPEG :: Movie.new (a.path) mm, ss = song.duration.divmod (60) .map | n | n.to_i.to_s.rjust (2, '0') a.meta ['duration'] = "# mm: # ss" a.meta ['bitrate'] = song.bitrate? song.bitrate / 1000: 0 end end

    Nota che qui sto usando un sentiero metodo, no url, perché a questo punto stiamo lavorando con un tempfile. Quindi estraggiamo la durata del brano (convertendolo in minuti e secondi con gli zeri iniziali) e il suo bitrate (convertendolo in kilobyte al secondo).

    Infine, mostra i metadati nella vista:

    views / canzoni / _song.html.erb

  • <%= audio_tag song.track.url, controls: true %> <%= song.title %> (<%= song.track.meta['duration'] %>, <%= song.track.meta['bitrate'] %>Kb / s)
  • Se controlli i contenuti sul / System / libellula pubblico cartella (il percorso predefinito per ospitare i caricamenti), ne annoti alcuni .yml file - stanno memorizzando tutte le meta informazioni in formato YAML.

    Distribuzione a Heroku

    L'ultimo argomento che tratteremo oggi è come preparare la tua applicazione prima di distribuirla sulla piattaforma cloud di Heroku. Il problema principale è che Heroku non ti consente di archiviare file personalizzati (come i caricamenti), quindi dobbiamo fare affidamento su un servizio di archiviazione su cloud come Amazon S3. Fortunatamente, Dragonfly può essere integrato facilmente con esso.

    Tutto ciò che devi fare è registrare un nuovo account su AWS (se non lo hai già), creare un utente con il permesso di accedere ai bucket S3 e annotare la coppia di chiavi dell'utente in un luogo sicuro. Potresti usare una coppia di chiavi di root, ma questo è davvero non consigliato. Infine, crea un bucket S3.

    Tornando alla nostra applicazione Rails, inserisci una nuova gemma:  

    Gemfile 

    gruppo: produzione gemma 'libellula-s3_data_store' fine

    Installalo:

    installazione bundle

    Quindi modificare la configurazione di Dragonfly per utilizzare S3 in un ambiente di produzione:

    config / inizializzatori / dragonfly.rb

    se Rails.env.production? datastore: s3, bucket_name: ENV ['S3_BUCKET'], access_key_id: ENV ['S3_KEY'], secret_access_key: ENV ['S3_SECRET'], regione: ENV ['S3_REGION'], url_scheme: 'https' altro datastore: file, root_path: Rails.root.join ('public / system / dragonfly', Rails.env), root_server: Rails.root.join ('pubblico') fine

    Fornire ENV variabili su Heroku, usa questo comando:

    heroku config: aggiungi SOME_KEY = SOME_VALUE

    Se si desidera testare l'integrazione con S3 localmente, è possibile utilizzare una gemma come dotenv-rails per gestire le variabili di ambiente. Ricorda, tuttavia, che la tua coppia di chiavi AWS non deve essere esposto pubblicamente!

    Un altro piccolo problema che ho incontrato durante l'implementazione su Heroku era l'assenza di FFmpeg. Il fatto è che quando viene creata una nuova applicazione Heroku, ha una serie di servizi che vengono comunemente utilizzati (ad esempio, ImageMagick è disponibile per impostazione predefinita). Altri servizi possono essere installati come componenti aggiuntivi di Heroku o sotto forma di buildpacks. Per aggiungere un buildpack FFmpeg, eseguire il seguente comando:

    buildpack heroku: aggiungi https://github.com/HYPERHYPER/heroku-buildpack-ffmpeg.git

    Ora tutto è pronto e puoi condividere la tua applicazione musicale con il mondo!

    Conclusione

    Questo è stato un lungo viaggio, non è vero? Oggi abbiamo discusso di Dragonfly, una soluzione per il caricamento di file in Rails. Abbiamo visto la sua configurazione di base, alcune opzioni di configurazione, generazione di miniature, elaborazione e memorizzazione dei metadati. Inoltre, abbiamo integrato Dragonfly con il servizio Amazon S3 e preparato la nostra applicazione per l'implementazione in produzione.

    Naturalmente, in questo articolo non abbiamo discusso tutti gli aspetti di Dragonfly, quindi assicuratevi di consultare il suo sito Web ufficiale per trovare un'ampia documentazione e utili esempi. Se hai altre domande o sei bloccato con alcuni esempi di codice, non esitare a contattarmi.

    Grazie per essere stato con me, e a presto!