Ladda upp filer med spår och slända

För en tid sedan skrev jag en artikel Ladda upp filer med skenor och helgedom som förklarade hur man introducerar en filuppladdningsfunktion i din Rails-applikation med hjälp av Shrine-pärlan. Det finns dock en massa liknande lösningar, och en av mina favoriter är Dragonfly-en lättanvänd uppladdningslösning för Rails and Rack skapad av Mark Evans. 

Vi täckte det här biblioteket i början av förra året men som med de flesta programvaror hjälper det att titta på bibliotek från tid till annan för att se vad som ändrats och hur vi kan använda det i vår ansökan.

I den här artikeln kommer jag att vägleda dig genom installationen av Dragonfly och förklara hur du använder de viktigaste funktionerna. Du kommer att lära dig hur:

  • Integrera Dragonfly i din ansökan
  • Konfigurera modeller för att arbeta med Dragonfly
  • Introducera en grundläggande uppladdningsmekanism
  • Introducera valideringar
  • Generera bildminnebilder
  • Utför filbehandling
  • Spara metadata för uppladdade filer
  • Förbered en applikation för implementering

För att göra sakerna mer intressanta, kommer vi att skapa en liten musikalisk applikation. Den kommer att presentera album och tillhörande låtar som kan hanteras och spelas upp på webbplatsen.

Källkoden för den här artikeln finns på GitHub. Du kan också kolla in den fungerande demo av ansökan.

Lista och hantera album

För att starta, skapa en ny Rails-applikation utan standard testpaket:

skenar nya UploadingWithDragonfly -T

För den här artikeln använder jag Rails 5, men de flesta av de beskrivna begreppen gäller även äldre versioner.

Skapa modell, kontroller och rutter

Vår lilla musikaliska webbplats kommer att innehålla två modeller: Album och Låt. För nu, låt oss skapa den första med följande fält:

  • titel (sträng) -Innehåller albumets titel
  • sångare (sträng) -albumets artister
  • image_uid (sträng)-ett specialfält för att lagra albumets förhandsgranskningsbild. Det här fältet kan namnges allt du vill, men det måste innehålla _uid suffix enligt instruktioner från Dragonfly-dokumentationen.

Skapa och tillämpa motsvarande migrering:

skenor g modell Album titel: strängsångare: sträng image_uid: strängskenor db: migrera

Låt oss nu skapa en mycket generisk kontroller för att hantera album med alla standardåtgärder:

albums_controller.rb

klassalbumkontrollen < 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

Slutligen lägg till rutterna:

config / routes.rb

resurser: album

Integrerande Dragonfly

Det är dags för Dragonfly att gå in i rampljuset. Lägg först pärlan i Gemfile:

Gemfile

pärla "slända"

Springa:

buntinstallationsskenor genererar slända

Det senare kommandot skapar en initierare som heter dragonfly.rb med standardkonfigurationen. Vi lägger det åt sidan för tillfället, men du kan läsa om olika alternativ på Dragonflys officiella hemsida.

Nästa viktiga sak är att utrusta vår modell med Dragonflys metoder. Detta görs genom att använda dragonfly_accessor:

modeller / album.rb

dragonfly_accessor: bild

Observera att jag säger här :bild-det hänför sig direkt till image_uid kolumn som vi skapade i föregående avsnitt. Om du till exempel namngav din kolumn photo_uid, sedan dragonfly_accessor metod skulle behöva ta emot :Foto som ett argument.

Om du använder Rails 4 eller 5, är ett annat viktigt steg att markera :bild fält (inte : image_uid!) som tillåtet i regulatorn:

albums_controller.rb

params.require (: album) .permit (: title,: sångare,: bild)

Det här är ganska mycket - vi är redo att skapa synpunkter och börja ladda upp våra filer!

Skapa visningar

Börja med indexvyn:

visningar / albumen / index.html.erb

album

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

Nu är det partiella:

visningar / albumen / _album.html.erb

  • <%= image_tag(album.image.url, alt: album.title) if album.image_stored? %> <%= link_to album.title, album_path(album) %> av <%= album.singer %> | <%= link_to 'Edit', edit_album_path(album) %> | <%= link_to 'Remove', album_path(album), method: :delete, data: confirm: 'Are you sure?' %>
  • Det finns två Dragonfly-metoder att notera här:

    • album.image.url returnerar sökvägen till bilden.
    • album.image_stored? säger om posten har en uppladdad fil på plats.

    Lägg nu till de nya och redigera sidorna:

    visningar / albumen / new.html.erb

    Lägg till album

    <%= render 'form' %>

    visningar / albumen / edit.html.erb

    Redigera <%= @album.title %>

    <%= render 'form' %>

    visningar / albumen / _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 %>

    Formen är inget fancy, men återigen notera att vi säger :bild, inte : image_uid, när filens inmatning görs.

    Nu kan du starta servern och testa uppladdningsfunktionen!

    Ta bort bilder

    Så användarna kan skapa och redigera album, men det finns ett problem: de har ingen möjlighet att ta bort en bild, bara för att ersätta den med en annan. Lyckligtvis är det här mycket enkelt att fixa genom att införa en kryssruta för "ta bort bild": 

    visningar / albumen / _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 %>

    Om albumet har en tillhörande bild visar vi den och gör en kryssruta. Om den här kryssrutan är inställd tas bilden bort. Observera att om ditt fält heter photo_uid, då kommer motsvarande metod att ta bort bifogad fil remove_photo. Enkelt, är det inte?

    Den enda andra sak att göra är att tillåta remove_image attribut i din controller:

    albums_controller.rb

    params.require (: album) .permit (: title,: sångare,: bild,: remove_image)

    Lägga till valideringar

    I detta skede fungerar allt bra, men vi kontrollerar inte användarens ingång alls, vilket inte är särskilt bra. Låt oss därför lägga till valideringar för albummodellen:

    modeller / album.rb

    validerar: titel, närvaro: true validates: sångare, närvaro: true validates: bild, närvaro: true validates_property: width, of:: image, in: (0 ... 900)

    validates_property är Dragonfly-metoden som kan kontrollera olika aspekter av din bilaga: du kan validera en filens filtillägg, MIME-typ, storlek etc.

    Låt oss nu skapa en generisk partiell för att göra de fel som hittades:

    visningar / delad / _errors.html.erb

    <% if object.errors.any? %> 

    Följande fel hittades:

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

    Anlita detta partiellt inuti formuläret:

    visningar / albumen / _form.html.erb

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

    Stil fälten med fel lite för att visuellt skildra dem:

    mallar / application.scss

    .field_with_errors display: inline; etikett färg: röd;  inmatning bakgrundsfärg: lightpink; 

    Behåll en bild mellan begäranden

    Vi har infört valideringar, vi löser ännu ett problem (ganska typiskt scenario, eh?): Om användaren har gjort fel när han fyller i formuläret måste han eller hon välja filen igen efter att ha klickat på Lämna knapp.

    Dragonfly kan hjälpa dig att lösa detta problem också med hjälp av en behöll_* dolt fält:

    visningar / albumen / _form.html.erb

    <%= f.hidden_field :retained_image %>

    Glöm inte att tillåta detta fält också:

    albums_controller.rb

    params.require (: album) .permit (: title,: sångare,: bild,: remove_image,: retained_image)

    Nu kommer bilden att fortsätta mellan förfrågningar! Det enda lilla problemet är emellertid att filuppladdningseffekten fortfarande kommer att visa meddelandet "välj ett fil", men det kan fixas med viss styling och ett streck av JavaScript.

    Bearbetar bilder

    Generera miniatyrbilder

    Bilderna uppladdade av våra användare kan ha mycket olika dimensioner, vilket kan (och förmodligen) få negativ inverkan på webbplatsens design. Du skulle förmodligen vilja skala bilder ner till vissa fasta dimensioner, och det är naturligtvis möjligt genom att använda bredd och höjd stilar. Detta är dock inte ett optimalt tillvägagångssätt: webbläsaren behöver fortfarande ladda ner fullstora bilder och krympa dem sedan.

    Ett annat alternativ (som vanligtvis är mycket bättre) är att generera bildminnebilder med några fördefinierade dimensioner på servern. Det här är väldigt enkelt att uppnå med Dragonfly:

    visningar / albumen / _album.html.erb

  • <%= image_tag(album.image.thumb('250x250#').url, alt: album.title) if album.image_stored? %> <%#… %>
  • 250x250 är givetvis dimensionerna, medan # är geometrin som betyder "ändra storlek och beskära om det behövs för att behålla bildförhållandet med mittens gravitation". Du kan hitta information om andra geometrier på Dragonflys hemsida.

    De tumme Metoden drivs av ImageMagick-en bra lösning för att skapa och manipulera bilder. För att du ska kunna se den fungerande demo lokalt måste du installera ImageMagick (alla större plattformar stöds). 

    Stöd för ImageMagick är aktiverat som standard inuti Dragonfly's initializer:

    config / initializers / dragonfly.rb

    plugin: imagemagick

    Nu skapas miniatyrer, men de lagras inte någonstans. Det innebär att varje gång en användare besöker albumsidan kommer miniatyrerna att regenereras. Det finns två sätt att övervinna detta problem: genom att generera dem efter att rekordet sparats eller genom att utföra generation i flyg.

    Det första alternativet innebär att du inför en ny kolumn för att lagra miniatyren och justera dragonfly_accessor metod. Skapa och tillämpa en ny migrering:

    rails g migration add_image_thumb_uid_to_albums image_thumb_uid: strängskenor db: migrera

    Ändra nu modellen:

    modeller / album.rb

    dragonfly_accessor: bilden gör copy_to (: image_thumb) | a | a.thumb ('250x250 #') avsluta dragonfly_accessor: image_thumb

    Observera att nu det första samtalet till dragonfly_accessor skickar ett block som faktiskt genererar miniatyren för oss och kopierar den till image_thumb. Använd nu bara image_thumb metod i dina åsikter:

    visningar / albumen / _album.html.erb

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

    Denna lösning är den enklaste, men det rekommenderas inte av de officiella dokumenten och vad som är värre vid skrivningstid fungerar det inte med behöll_* fält.

    Låt mig därför visa dig ett annat alternativ: generera miniatyrbilder i fluga. Det innebär att skapa en ny modell och tweaking Dragonfly's config-fil. Först, modellen:

    skenor g modell Thumb uid: string job: string rake db: migrera

    De tummen bordet kommer att vara värd för dina miniatyrbilder, men de kommer att genereras på begäran. För att detta ska ske måste vi omdefiniera url metod inuti Dragonfly-initieraren:

    config / initializers / dragonfly.rb

    Dragonfly.app.configure gör define_url göra | app, jobb, opts | thumb = Thumb.find_by_job (job.signature) om thumb app.datastore.url_for (thumb.uid,: scheme => 'https') annars app.server.url_for (jobb) slutändan before_serve do | job, env | uid = job.store Thumb.create! (: uid => uid,: job => job.signature) slutet # ... slutet

    Lägg nu till ett nytt album och besök rotsidan. Första gången du gör det kommer följande utskrift att skrivas ut i loggarna:

    DRAGONFLY: shell command: "convert" "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 "

    Det innebär att miniatyrbilden genereras för oss av ImageMagick. Om du laddar om sidan, kommer den här raden inte att visas längre, vilket innebär att miniatyren har cachat! Du kan läsa lite mer om den här funktionen på Dragonflys hemsida.

    Mer bearbetning

    Du kan utföra praktiskt taget någon manipulering av dina bilder efter att de har laddats upp. Detta kan göras inuti after_assign ring tillbaka. Låt oss till exempel konvertera alla våra bilder till JPEG-format med 90% kvalitet: 

    dragonfly_accessor: bilden gör after_assign | a | a.encode! ('jpg', '-quality 90') slutet

    Det finns många fler åtgärder du kan utföra: rotera och beskär bilderna, koda med ett annat format, skriv text på dem, mixa med andra bilder (till exempel placera ett vattenstämpel), etc. För att se några andra exempel, se ImageMagick-avsnittet på Dragonfly-webbplatsen.

    Överföring och hantering av låtar

    Naturligtvis är huvuddelen av vår musikaliska webbplats låtar, så låt oss lägga till dem nu. Varje låt har en titel och en musikalisk fil, och den hör till ett album:

    skenor g modell Sångalbum: belong_to title: string track_uid: strängskenor db: migrera

    Haka upp Dragonfly-metoderna, som vi gjorde för Album modell:

    modeller / song.rb

    dragonfly_accessor: track

    Glöm inte att skapa en har många relation:

    modeller / album.rb

    has_many: låtar, beroende:: förstör

    Lägg till nya rutter. En sång finns alltid i ett albums räckvidd, så jag ska göra dessa rutter nestade:

    config / routes.rb

    resurser: album gör resurser: låtar, bara: [: ny,: skapa] slut

    Skapa en mycket enkel kontroller (återigen, glöm inte att tillåta Spår fält):

    songs_controller.rb

    klass 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

    Visa låtarna och en länk för att lägga till en ny:

    visningar / albumen / show.html.erb

    <%= @album.title %>

    av <%= @album.singer %>

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

    Koda formuläret:

    visningar / låtar / new.html.erb

    Lägg till låt till <%= @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 %>

    Slutligen lägg till _låt partiell:

    visningar / låtar / _song.html.erb

  • <%= audio_tag song.track.url, controls: true %> <%= song.title %>
  • Här använder jag HTML5 audio tagg som inte fungerar för äldre webbläsare. Så, om du syftar till att stödja sådana webbläsare, använd en polyfil.

    Som du ser är hela processen väldigt enkel. Dragonfly bryr sig inte riktigt vilken typ av fil du vill ladda upp. allt du behöver göra är att ge en dragonfly_accessor metod, lägg till ett korrekt fält, tillåt det och gör en filinmatningstagg.

    Lagring av metadata

    När jag öppnar en spellista förväntar jag mig att se ytterligare information om varje låt, som dess längd eller bitrate. Naturligtvis är denna information som standard inte lagrad någonstans, men vi kan ordna det ganska enkelt. Dragonfly tillåter oss att ge ytterligare data om varje uppladdad fil och hämta den senare med hjälp av meta metod.

    Saker är dock lite mer komplexa när vi arbetar med ljud eller video, för att hämta deras metadata krävs en speciell pärla streamio-ffmpeg. Denna pärla är i sin tur beroende av FFmpeg, så för att kunna fortsätta måste du installera den på din dator.

    Lägg till Streamio-ffmpeg in i Gemfile:

    Gemfile

    pärla 'streamio-ffmpeg'

    Installera det:

    buntinstallation

    Nu kan vi använda samma after_assign återuppringning redan sett i föregående avsnitt:

    modeller / song.rb

    dragonfly_accessor: spår gör after_assign do | a | låt = 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 slutet slutet

    Observera att här använder jag en väg metod, inte url, för att vi just nu arbetar med en tempfile. Nästa extraherar vi bara låtens varaktighet (konvertera den till minuter och sekunder med ledande nollor) och dess bitrate (konvertera den till kilobyte per sekund).

    Slutligen, visa metadata i vyn:

    visningar / låtar / _song.html.erb

  • <%= audio_tag song.track.url, controls: true %> <%= song.title %> (<%= song.track.meta['duration'] %>, <%= song.track.meta['bitrate'] %>Kb / s)
  • Om du kontrollerar innehållet på publika / system / trollslända mapp (standardplatsen som värd för uppladdningarna), noterar du några .YML filer - de lagrar all metainformation i YAML-format.

    Utplacering till Heroku

    Det sista ämnet vi kommer att täcka idag är hur du förbereder din ansökan innan du distribuerar till Heroku Cloud Platform. Det största problemet är att Heroku inte tillåter dig att lagra anpassade filer (som uppladdningar), så vi måste förlita oss på en cloud storage-tjänst som Amazon S3. Lyckligtvis kan Dragonfly enkelt integreras med det.

    Allt du behöver göra är att registrera ett nytt konto hos AWS (om du inte redan har det), skapa en användare med tillstånd att komma åt S3-hinkar och skriv ner användarens nyckelpar på en säker plats. Du kan använda ett rotnyckelpar, men det här är verkligen rekommenderas inte. Slutligen skapa en S3 hink.

    Gå tillbaka till vår Rails-applikation, släpp in en ny pärla:  

    Gemfile 

    grupp: tillverkning gör perle "dragonfly-s3_data_store" slutet

    Installera det:

    buntinstallation

    Därefter tweak Dragonflys konfiguration för att använda S3 i en produktionsmiljö:

    config / initializers / dragonfly.rb

    om Rails.env.production? datastore: s3, bucket_name: ENV ['S3_BUCKET'], access_key_id: ENV ['S3_KEY'], secret_access_key: ENV ['S3_SECRET'], region: ENV ['S3_REGION'], url_scheme: 'https' annars datastore: root_path: Rails.root.join ("public / system / dragonfly", Rails.env), server_root: Rails.root.join ("public") slutet

    Att förse ENV variabler på Heroku, använd det här kommandot:

    heroku config: lägg till SOME_KEY = SOME_VALUE

    Om du vill testa integration med S3 lokalt kan du använda en pärla som dotenv-skenor för att hantera miljövariabler. Kom ihåg dock att ditt AWS-nyckelpar får inte vara offentligt utsatta!

    Ett annat litet problem som jag har stött på när jag genomförde till Heroku var frånvaron av FFmpeg. Saken är att när en ny Heroku-applikation skapas, har den en uppsättning tjänster som vanligtvis används (till exempel är ImageMagick tillgängligt som standard). Övriga tjänster kan installeras som Heroku addons eller i form av buildpacks. För att lägga till en FFmpeg buildpack, kör följande kommando:

    heroku buildpacks: lägg till https://github.com/HYPERHYPER/heroku-buildpack-ffmpeg.git

    Nu är allt klart, och du kan dela din musikaliska applikation med världen!

    Slutsats

    Det här var en lång resa, eller hur? Idag har vi diskuterat Dragonfly-en lösning för filuppladdning i Rails. Vi har sett sin grundläggande inställning, vissa konfigurationsalternativ, miniatyrbildsgenerering, bearbetning och lagring av metadata. Vi har också integrerat Dragonfly med Amazon S3-tjänsten och förberett vår applikation för distribuering vid produktion.

    Naturligtvis har vi inte diskuterat alla aspekter av Dragonfly i den här artikeln, så se till att bläddra i sin officiella hemsida för att hitta omfattande dokumentation och användbara exempel. Om du har några andra frågor eller är fast med några kodexempel, tveka inte att kontakta mig.

    Tack för att du bodde hos mig och vi ses snart!