Laster opp filer med Rails og Dragonfly

For en tid siden skrev jeg en artikkel Last opp filer med skinner og helligdom som forklarte hvordan du introduserer en filopplastingsfunksjon i din Rails-applikasjon ved hjelp av Shrine-perlen. Det finnes imidlertid en rekke liknende løsninger, og en av mine favoritter er Dragonfly-en brukervennlig opplastingsløsning for Rails and Rack opprettet av Mark Evans. 

Vi dekket dette biblioteket tidlig i fjor, men som med de fleste programvare hjelper det å se på biblioteker fra tid til annen for å se hva som er endret og hvordan vi kan bruke det i vår søknad.

I denne artikkelen vil jeg veilede deg gjennom oppsettet av Dragonfly og forklare hvordan du bruker de viktigste funksjonene. Du vil lære hvordan:

  • Integrer Dragonfly i din søknad
  • Konfigurer modeller for å jobbe med Dragonfly
  • Sett inn en grunnleggende opplastingsmekanisme
  • Innfør valideringer
  • Generer bildeminiatyrer
  • Utfør filbehandling
  • Lagre metadata for opplastede filer
  • Forbered et program for distribusjon

For å gjøre ting mer interessant, skal vi lage en liten musikalsk applikasjon. Det vil presentere album og tilhørende sanger som kan styres og spilles på nettsiden.

Kildekoden for denne artikkelen er tilgjengelig på GitHub. Du kan også sjekke ut den aktive demoen i søknaden.

Oppføring og administrering av album

For å starte, opprett et nytt Rails-program uten standard testing suite:

skinner nye UploadingWithDragonfly -T

For denne artikkelen vil jeg bruke Rails 5, men de fleste av de beskrevne konseptene gjelder også for eldre versjoner.

Opprette modell, kontroller og ruter

Vårt lille musikalske nettsted skal inneholde to modeller: Album og Sang. For nå, la oss lage den første med følgende felt:

  • tittel (string)-inneholder albumets tittel
  • sanger (string) -albumets utøver
  • image_uid (string) - Et spesielt felt for å lagre albumets forhåndsvisningsbilde. Dette feltet kan bli navngitt alt du liker, men det må inneholde _uid suffiks som beskrevet av Dragonfly-dokumentasjonen.

Opprett og bruk tilsvarende migrering:

skinner g modell Album tittel: streng sanger: streng image_uid: strengskinner db: migrere

La oss nå lage en veldig generell kontroller for å administrere album med alle standardhandlinger:

albums_controller.rb

klassen AlbumsController < 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

Til slutt legger du til ruttene:

config / routes.rb

ressurser: album

Integrerer Dragonfly

Det er på tide for Dragonfly å gå inn i rampelyset. Først legger du perlen til Gemfile:

Gemfile

perle 'dragonfly'

Løpe:

bunt installasjonsskinner genererer dragonfly

Sistnevnte kommando vil opprette en initialiserer som heter dragonfly.rb med standardkonfigurasjonen. Vi vil legge den til side for nå, men du kan lese om forskjellige alternativer på Dragonflys offisielle nettside.

Den neste viktige tingen å gjøre er å utruste vår modell med Dragonfly-metoder. Dette gjøres ved å bruke dragonfly_accessor:

modeller / album.rb

dragonfly_accessor: image

Merk at her sier jeg :bilde-det relaterer seg direkte til image_uid kolonne som vi opprettet i forrige seksjon. Hvis du for eksempel oppgav kolonnen din photo_uid, og så dragonfly_accessor metoden vil trenge å motta : bilde som et argument.

Hvis du bruker Rails 4 eller 5, er et annet viktig trinn å markere :bilde felt (ikke : image_uid!) som tillatt i kontrolleren:

albums_controller.rb

params.require (: album) .permit (: title,: sanger,: bilde)

Dette er ganske mye det - vi er klare til å lage visninger og begynne å laste opp våre filer!

Opprette visninger

Start med indeksvisningen:

visninger / album / index.html.erb

album

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

Nå er den delvise:

visninger / album / _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 er to Dragonfly-metoder å merke seg her:

    • album.image.url returnerer banen til bildet.
    • album.image_stored? sier om posten har en opplastet fil på plass.

    Legg nå til de nye og redigere sidene:

    visninger / album / new.html.erb

    Legg til album

    <%= render 'form' %>

    visninger / album / edit.html.erb

    Redigere <%= @album.title %>

    <%= render 'form' %>

    visninger / 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 %>

    Skjemaet er ikke noe fancy, men igjen merke til at vi sier :bilde, ikke : image_uid, når gjengivelse av filinngangen.

    Nå kan du starte opp serveren og teste opplastingsfunksjonen!

    Fjerner bilder

    Så brukerne kan lage og redigere album, men det er et problem: de har ingen måte å fjerne et bilde, bare for å erstatte det med en annen. Heldigvis er dette veldig enkelt å fikse ved å introdusere en "fjern bilde" -boks: 

    visninger / 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 %>

    Hvis albumet har et tilhørende bilde, viser vi det og gjengir en avkrysningsboks. Hvis denne avkrysningsboksen er satt, vil bildet bli fjernet. Merk at hvis feltet ditt er oppkalt photo_uid, da vil den tilsvarende metoden for å fjerne vedlegg være remove_photo. Enkelt, er det ikke?

    Den eneste andre tingen å gjøre er å tillate remove_image attributt i kontrolleren din:

    albums_controller.rb

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

    Legge til valideringer

    På dette stadiet virker alt bra, men vi kontrollerer ikke brukerens innspill i det hele tatt, noe som ikke er spesielt bra. Derfor la vi legge til valideringer for albummodellen:

    modeller / album.rb

    validerer: tittel, tilstedeværelse: ekte validerer: sanger, tilstedeværelse: ekte validerer: bilde, tilstedeværelse: sant validates_property: width, of:: image, in: (0 ... 900)

    validates_property er Dragonfly-metoden som kan kontrollere ulike aspekter av vedlegget ditt: Du kan validere en fils utvidelse, MIME-type, størrelse osv..

    La oss nå lage en generisk del for å gjengi feilene som ble funnet:

    visninger / delt / _errors.html.erb

    <% if object.errors.any? %> 

    Følgende feil ble funnet:

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

    Bruk denne delen i skjemaet:

    visninger / album / _form.html.erb

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

    Stil feltene med feil litt for å visuelt skildre dem:

    stilark / application.scss

    .field_with_errors display: inline; etikett farge: rød;  input bakgrunnsfargen: lightpink; 

    Behold et bilde mellom forespørsler

    Etter å ha innført valideringer, løper vi inn enda et problem (ganske typisk scenario, eh?): Hvis brukeren har gjort feil mens du fyller ut skjemaet, må han eller hun velge filen igjen etter å ha klikket på Sende inn knapp.

    Dragonfly kan også hjelpe deg med å løse dette problemet ved å bruke en retained_ * skjult felt:

    visninger / album / _form.html.erb

    <%= f.hidden_field :retained_image %>

    Ikke glem å tillate dette feltet også:

    albums_controller.rb

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

    Nå vil bildet være vedvarende mellom forespørsler! Det eneste lille problemet er imidlertid at filopplastingsinngangen fortsatt vil vise meldingen "velg en fil", men dette kan løses med noe styling og et dash av JavaScript.

    Behandler bilder

    Genererer miniatyrbilder

    Bildene lastet opp av brukerne våre, kan ha svært forskjellige dimensjoner, noe som kan (og sannsynligvis vil) føre til en negativ innvirkning på nettstedets design. Du vil sannsynligvis skalere bilder ned til noen faste dimensjoner, og selvfølgelig er dette mulig ved å bruke bredde og høyde stiler. Dette er imidlertid ikke en optimal tilnærming: nettleseren må fortsatt laste ned fullstørrelsesbilder og krympe dem.

    Et annet alternativ (som vanligvis er mye bedre) er å generere bildeminiatyrer med noen forhåndsdefinerte dimensjoner på serveren. Dette er veldig enkelt å oppnå med Dragonfly:

    visninger / album / _album.html.erb

  • <%= image_tag(album.image.thumb('250x250#').url, alt: album.title) if album.image_stored? %> <%#… %>
  • 250x250 er selvsagt dimensjonene, mens # er geometrien som betyr "endre størrelse og beskjære om nødvendig for å opprettholde aspektforholdet med senterets tyngdekraften". Du kan finne informasjon om andre geometrier på Dragonflys nettsted.

    De tommel Metoden drives av ImageMagick-en flott løsning for å lage og manipulere bilder. Derfor, for å se den fungerende demoen lokalt, må du installere ImageMagick (alle de store plattformene støttes). 

    Støtte for ImageMagick er aktivert som standard inne i Dragonfly's initializer:

    konfig / initializers / dragonfly.rb

    plugin: imagemagick

    Nå genereres miniatyrbilder, men de lagres ikke hvor som helst. Dette betyr at hver gang en bruker besøker albumsiden, blir miniatyrbildene regenerert. Det er to måter å overvinne dette problemet ved å generere dem etter at posten er lagret eller ved å utføre generasjon på fly.

    Det første alternativet innebærer å innføre en ny kolonne for å lagre miniatyrbildet og justere dragonfly_accessor metode. Opprett og bruk en ny migrering:

    skinner g migrasjon add_image_thumb_uid_to_albums image_thumb_uid: strengskinner db: migrere

    Modifiser nå modellen:

    modeller / album.rb

    dragonfly_accessor: bildet gjør copy_to (: image_thumb) | a | a.thumb ('250x250 #') slutt dragonfly_accessor: image_thumb

    Legg merke til at nå det første anropet til dragonfly_accessor sender en blokk som faktisk genererer miniatyrbildet for oss og kopierer det til image_thumb. Bruk bare image_thumb metode i dine synspunkter:

    visninger / album / _album.html.erb

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

    Denne løsningen er den enkleste, men det anbefales ikke av de offisielle doksene, og hva er verre, når det skrives, virker det ikke med retained_ * Enger.

    Derfor, la meg vise deg et annet alternativ: generere miniatyrbilder i fly. Det innebærer å skape en ny modell og tilpasse Dragonfly's config-fil. Først, modellen:

    skinner g modell Thumb uid: string jobb: streng rake db: migrere

    De tommelen bordet vil være vert for miniatyrbildene dine, men de vil bli generert på forespørsel. For å få dette til å skje, må vi omdefinere url metode inne i Dragonfly initializer:

    konfig / initializers / dragonfly.rb

    Dragonfly.app.configure gjør define_url gjør | app, jobb, opts | thumb = Thumb.find_by_job (job.signature) hvis tommelen app.datastore.url_for (thumb.uid,: scheme => 'https') annet app.server.url_for (jobb) slutten end_serve do | job, env | uid = job.store Thumb.create! (: uid => uid,: jobb => jobb.signatur) slutt # ... ende

    Legg nå et nytt album og besøk rotsiden. Første gang du gjør det, blir følgende utskrift skrevet ut i loggene:

    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 "

    Dette betyr at miniatyrbildet genereres for oss av ImageMagick. Hvis du oppdaterer siden, vises denne linjen ikke lenger, noe som betyr at miniatyrbildet ble cached! Du kan lese litt mer om denne funksjonen på Dragonflys nettsted.

    Mer behandling

    Du kan utføre nesten hvilken som helst manipulasjon av bildene dine etter at de ble lastet opp. Dette kan gjøres inne i after_assign Ring tilbake. La oss for eksempel konvertere alle våre bilder til JPEG-format med 90% kvalitet: 

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

    Det er mange flere handlinger du kan utføre: roter og beskjær bildene, koder med et annet format, skriv tekst på dem, bland med andre bilder (for eksempel å plassere et vannmerke) etc. For å se noen andre eksempler, se ImageMagick-delen på Dragonfly-nettstedet.

    Laster opp og administrerer sanger

    Selvfølgelig er hoveddelen av vårt musikalske nettsted sanger, så la oss legge til dem nå. Hver sang har en tittel og en musikalsk fil, og den tilhører et album:

    skinner g modell Sangalbum: belongs_to title: string track_uid: strengskinner db: migrere

    Koble til Dragonfly-metodene, som vi gjorde for Album modell:

    modeller / song.rb

    dragonfly_accessor: spor

    Ikke glem å etablere en har mange forhold:

    modeller / album.rb

    has_many: sanger, avhengig:: ødelegge

    Legg til nye ruter. En sang eksisterer alltid i omfanget av et album, så jeg vil gjøre disse ruterne nestet:

    config / routes.rb

    ressurser: album gjør ressurser: sanger, bare: [: ny,: opprett] slutt

    Lag en veldig enkel kontroller (igjen, ikke glem å tillate spor felt):

    songs_controller.rb

    klasse 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

    Vis sangene og en lenke for å legge til en ny:

    visninger / album / show.html.erb

    <%= @album.title %>

    av <%= @album.singer %>

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

    Kode skjemaet:

    visninger / sanger / new.html.erb

    Legg til sang til <%= @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 %>

    Til slutt legger du til _sang delvis:

    visninger / sanger / _song.html.erb

  • <%= audio_tag song.track.url, controls: true %> <%= song.title %>
  • Her bruker jeg HTML5 lyd tag, som ikke fungerer for eldre nettlesere. Så, hvis du har som mål å støtte slike nettlesere, bruk en polyfil.

    Som du ser, er hele prosessen veldig enkel. Dragonfly bryr seg ikke akkurat hva slags fil du ønsker å laste opp; alt du trenger å gjøre er å gi en dragonfly_accessor metode, legg til et riktig felt, tillat det, og gjeng en filinngangstag.

    Lagre metadata

    Når jeg åpner en spilleliste, forventer jeg å se ytterligere informasjon om hver sang, som dens varighet eller bithastighet. Selvfølgelig er denne informasjonen som standard ikke lagret hvor som helst, men vi kan ordne det ganske enkelt. Dragonfly lar oss gi ytterligere data om hver opplastede fil og hente den senere ved å bruke meta metode.

    Ting er imidlertid litt mer komplekse når vi jobber med lyd eller video, fordi å hente metadata er det behov for en spesiell perle streamio-ffmpeg. Denne perlen er igjen avhengig av FFmpeg, så for å kunne fortsette må du installere den på PCen.

    Legg til streamio-ffmpeg inn i det Gemfile:

    Gemfile

    perle 'streamio-ffmpeg'

    Installer den:

    bunt installasjon

    Nå kan vi ansette det samme after_assign tilbakeringing allerede sett i forrige seksjoner:

    modeller / song.rb

    dragonfly_accessor: track do after_assign do | a | sang = 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-enden

    Merk at her bruker jeg en sti metode, ikke url, fordi på dette punktet jobber vi med en tempfile. Deretter pakker vi bare ut sangens varighet (konvertere den til minutter og sekunder med ledende nuller) og bitrate (konvertere den til kilobytes per sekund).

    Til slutt, vis metadata i visningen:

    visninger / sanger / _song.html.erb

  • <%= audio_tag song.track.url, controls: true %> <%= song.title %> (<%= song.track.meta['duration'] %>, <%= song.track.meta['bitrate'] %>Kb / s)
  • Hvis du sjekker innholdet på offentlig / system / øyenstikker mappe (standardplassering som vert for opplastingene), vil du legge merke til noen .YML filer - de lagrer all metainformasjon i YAML-format.

    Utplassering til Heroku

    Det siste emnet vi skal dekke i dag, er hvordan du klargjør søknaden din før du distribuerer til Heroku skyplattform. Hovedproblemet er at Heroku ikke tillater deg å lagre egendefinerte filer (som opplastinger), så vi må stole på en skytholdingsservice som Amazon S3. Heldigvis kan Dragonfly integreres med det enkelt.

    Alt du trenger å gjøre er å registrere en ny konto hos AWS (hvis du ikke allerede har det), opprett en bruker med tillatelse til å få tilgang til S3-buckets, og skriv ned brukerens nøkkelpar på et trygt sted. Du kan bruke et rotnøkkelpar, men dette er egentlig ikke anbefalt. Til slutt, lag en S3 bøtte.

    Gå tilbake til programmet Rails, sett inn en ny perle:  

    Gemfile 

    gruppe: produksjon gjør perle 'dragonfly-s3_data_store' slutten

    Installer den:

    bunt installasjon

    Deretter justerer du Dragonfly-konfigurasjonen for å bruke S3 i et produksjonsmiljø:

    konfig / initializers / dragonfly.rb

    hvis 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' annet datastore: root_path: Rails.root.join ('public / system / dragonfly', Rails.env), server_root: Rails.root.join ('public') slutten

    Å skaffe ENV variabler på Heroku, bruk denne kommandoen:

    heroku config: legg til SOME_KEY = SOME_VALUE

    Hvis du ønsker å teste integrasjon med S3 lokalt, kan du bruke en perle som dotenv-skinner til å håndtere miljøvariabler. Husk imidlertid at ditt AWS nøkkelpar må ikke være offentlig eksponert!

    Et annet lite problem jeg har kjørt inn i mens du distribuerer til Heroku, var fraværet av FFmpeg. Saken er at når et nytt Heroku-program blir opprettet, har det et sett av tjenester som vanligvis brukes (for eksempel, ImageMagick er tilgjengelig som standard). Andre tjenester kan installeres som Heroku addons eller i form av buildpacks. For å legge til en FFmpeg buildpack, kjør følgende kommando:

    heroku buildpacks: legg til https://github.com/HYPERHYPER/heroku-buildpack-ffmpeg.git

    Nå er alt klart, og du kan dele din musikalske applikasjon med verden!

    Konklusjon

    Dette var en lang reise, ikke sant? I dag har vi diskutert Dragonfly-en løsning for filopplasting i Rails. Vi har sett sitt grunnleggende oppsett, noen konfigurasjonsalternativer, miniatyr generering, behandling og metadata lagring. Vi har også integrert Dragonfly med Amazon S3-tjenesten og utarbeidet vårt program for distribusjon på produksjon.

    Selvfølgelig har vi ikke diskutert alle aspekter av Dragonfly i denne artikkelen, så sørg for å bla gjennom sitt offisielle nettsted for å finne omfattende dokumentasjon og nyttige eksempler. Hvis du har andre spørsmål eller er fast med noen kodeeksempler, ikke nøl med å kontakte meg.

    Takk for at du bodde hos meg og ser deg snart!