Bestanden uploaden met Rails en Dragonfly

Enige tijd geleden schreef ik een artikel Uploading Files With Rails and Shrine dat uitlegde hoe je een functie voor het uploaden van bestanden in je Rails-applicatie introduceerde met behulp van de Shrine-edelsteen. Er zijn echter een hele reeks vergelijkbare oplossingen beschikbaar, en een van mijn favorieten is Dragonfly, een eenvoudig te gebruiken uploadoplossing voor Rails en Rack, gemaakt door Mark Evans.. 

We hebben deze bibliotheek begin vorig jaar besproken, maar zoals met de meeste software helpt het om van tijd tot tijd een kijkje te nemen bij bibliotheken om te zien wat er is veranderd en hoe we het kunnen gebruiken in onze applicatie..

In dit artikel zal ik je helpen met het instellen van Dragonfly en uitleggen hoe je de belangrijkste functies ervan kunt gebruiken. Je leert hoe je:

  • Integreer Dragonfly in uw applicatie
  • Configureer modellen om met Dragonfly te werken
  • Introduceer een basis uploadmechanisme
  • Introduceer validaties
  • Genereer afbeeldingsminiaturen
  • Voer bestandsverwerking uit
  • Bewaar metadata voor geüploade bestanden
  • Maak een aanvraag voor implementatie

Om de dingen interessanter te maken, gaan we een kleine muzikale toepassing maken. Het bevat albums en bijbehorende nummers die kunnen worden beheerd en afgespeeld op de website.

De broncode voor dit artikel is beschikbaar op GitHub. Je kunt ook de werkende demo van de applicatie bekijken.

Albums toevoegen en beheren

Maak om te beginnen een nieuwe Rails-applicatie zonder de standaard testsuite:

rails nieuw UploadingWithDragonfly -T

Voor dit artikel zal ik Rails 5 gebruiken, maar de meeste van de beschreven concepten zijn ook van toepassing op oudere versies.

Model, controller en routes maken

Onze kleine muzikale site zal twee modellen bevatten: Album en lied. Laten we voorlopig de eerste maken met de volgende velden:

  • titel (draad) - bevat de titel van het album
  • zanger (draad) -album's artiest
  • image_uid (draad) - een speciaal veld om de voorbeeldafbeelding van het album op te slaan. Dit veld kan elke naam krijgen die u wilt, maar het moet de bevatten _UID achtervoegsel volgens de instructies in de Dragonfly-documentatie.

Maak en pas de bijbehorende migratie toe:

rails g model Albumtitel: string singer: string image_uid: stringrails db: migreren

Laten we nu een zeer generieke controller maken om albums te beheren met alle standaardacties:

albums_controller.rb

klasse 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

Voeg ten slotte de routes toe:

config / routes.rb

middelen: albums

Dragonfly integreren

Het is tijd voor Dragonfly om in de schijnwerpers te komen. Voeg eerst de edelsteen toe aan de Gemfile:

Gemfile

gem 'dragonfly'

Rennen:

bundel installeren rails genereren van libel

Met de laatste opdracht wordt een initializer met de naam gemaakt dragonfly.rb met de standaardconfiguratie. We zullen het voor nu opzij zetten, maar je kunt over verschillende opties lezen op de officiële website van Dragonfly.

Het volgende belangrijke ding om te doen is ons model uitrusten met de methoden van Dragonfly. Dit wordt gedaan met behulp van de dragonfly_accessor:

modellen / album.rb

dragonfly_accessor: afbeelding

Merk op dat ik hier zeg :beeld-het heeft direct betrekking op de image_uid kolom die we in het vorige gedeelte hebben gemaakt. Als u bijvoorbeeld uw kolom hebt genoemd photo_uid, dan de dragonfly_accessor methode zou moeten ontvangen :foto als een argument.

Als u Rails 4 of 5 gebruikt, is een andere belangrijke stap het markeren van de :beeld veld (niet : image_uid!) zoals toegestaan ​​in de controller:

albums_controller.rb

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

Dit is zo ongeveer het - we zijn klaar om meningen te creëren en beginnen met het uploaden van onze bestanden!

Aanzichten maken

Begin met de indexweergave:

views / albums / index.html.erb

albums

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

Nu de gedeeltelijke:

views / albums / _album.html.erb

  • <%= image_tag(album.image.url, alt: album.title) if album.image_stored? %> <%= link_to album.title, album_path(album) %> door <%= album.singer %> | <%= link_to 'Edit', edit_album_path(album) %> | <%= link_to 'Remove', album_path(album), method: :delete, data: confirm: 'Are you sure?' %>
  • Er zijn twee Dragonfly-methoden om op te merken:

    • album.image.url geeft het pad naar de afbeelding.
    • album.image_stored? zegt of de record een geüploade bestand heeft.

    Voeg nu de nieuwe en bewerk pagina's toe:

    views / albums / new.html.erb

    Voeg album toe

    <%= render 'form' %>

    views / albums / edit.html.erb

    Bewerk <%= @album.title %>

    <%= render 'form' %>

    views / albums / _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 %>

    De vorm is niets speciaals, maar merk nogmaals op dat we het zeggen :beeld, niet : image_uid, bij het renderen van de bestandsinvoer.

    Nu kunt u de server opstarten en de uploadfunctie testen!

    Afbeeldingen verwijderen

    De gebruikers kunnen dus albums maken en bewerken, maar er is een probleem: ze kunnen de afbeelding niet verwijderen, maar alleen vervangen door een andere. Gelukkig is dit heel gemakkelijk te repareren door een selectievakje "afbeelding verwijderen" in te voeren: 

    views / albums / _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 %>

    Als het album een ​​bijbehorende afbeelding heeft, wordt dit weergegeven en wordt een selectievakje weergegeven. Als dit selectievakje is ingeschakeld, wordt de afbeelding verwijderd. Merk op dat als uw veld een naam heeft photo_uid, dan zal de overeenkomstige methode om bijlage te verwijderen zijn Verwijder foto. Eenvoudig, is het niet?

    Het enige andere dat u hoeft te doen, is het toestaan ​​van remove_image kenmerk in uw controller:

    albums_controller.rb

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

    Validaties toevoegen

    In deze fase werkt alles goed, maar we controleren de invoer van de gebruiker helemaal niet, wat niet bijzonder goed is. Laten we daarom validaties toevoegen voor het albummodel:

    modellen / album.rb

    validates: title, presence: true validates: singer, presence: true validates: image, presence: true validates_property: width, of:: image, in: (0 ... 900)

    validates_property is de Dragonfly-methode die verschillende aspecten van uw bijlage kan controleren: u kunt de extensie, het MIME-type, de grootte, enzovoort van een bestand valideren.

    Laten we nu een generieke partitie maken om de gevonden fouten weer te geven:

    views / shared / _errors.html.erb

    <% if object.errors.any? %> 

    De volgende errors zijn gevonden:

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

    Gebruik dit deel in het formulier:

    views / albums / _form.html.erb

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

    Stijl de velden met fouten een beetje om ze visueel weer te geven:

    stylesheets / application.scss

    .field_with_errors display: inline; label kleur: rood;  invoer achtergrondkleur: lichtroze; 

    Het opslaan van een afbeelding tussen aanvragen

    Nadat we validaties hebben geïntroduceerd, komen we nog een ander probleem tegen (nogal een typisch scenario, nietwaar?): Als de gebruiker tijdens het invullen van het formulier fouten heeft gemaakt, moet hij of zij het bestand opnieuw kiezen nadat hij op de knop heeft geklikt. voorleggen knop.

    Dragonfly kan je ook helpen dit probleem op te lossen door een a te gebruiken retained_ * verborgen veld:

    views / albums / _form.html.erb

    <%= f.hidden_field :retained_image %>

    Vergeet ook niet om dit veld toe te staan:

    albums_controller.rb

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

    Nu zal de afbeelding tussen verzoeken blijven bestaan! Het enige kleine probleem is echter dat de bestandsuploadinvoer nog steeds het bericht 'kies een bestand' weergeeft, maar dit kan worden opgelost met wat styling en een scheutje JavaScript.

    Afbeeldingen verwerken

    Miniaturen genereren

    De afbeeldingen die door onze gebruikers worden geüpload, kunnen zeer verschillende dimensies hebben, wat een negatief effect kan (en waarschijnlijk zal hebben) op het ontwerp van de website. Je wilt waarschijnlijk afbeeldingen verkleinen tot een aantal vaste dimensies, en natuurlijk is dit mogelijk door gebruik te maken van de breedte en hoogte stijlen. Dit is echter geen optimale aanpak: de browser moet afbeeldingen op volledig formaat nog steeds downloaden en vervolgens verkleinen.

    Een andere optie (die meestal veel beter is) is om miniaturen van afbeeldingen te genereren met een aantal vooraf gedefinieerde dimensies op de server. Dit is heel eenvoudig te bereiken met Dragonfly:

    views / albums / _album.html.erb

  • <%= image_tag(album.image.thumb('250x250#').url, alt: album.title) if album.image_stored? %> <%#… %>
  • 250x250 is, natuurlijk, de dimensies, terwijl # is de geometrie die betekent "formaat wijzigen en bijsnijden indien nodig om de beeldverhouding te behouden met zwaartekracht in het midden". Mogelijk vindt u informatie over andere geometrieën op de website van Dragonfly.

    De duim methode wordt mogelijk gemaakt door ImageMagick - een geweldige oplossing voor het maken en manipuleren van afbeeldingen. Om de werkende demo lokaal te kunnen zien, moet je daarom ImageMagick installeren (alle belangrijke platforms worden ondersteund). 

    Ondersteuning voor ImageMagick is standaard ingeschakeld in de initialisatie van Dragonfly:

    config / initialiseerders / dragonfly.rb

    plugin: imagemagick

    Nu worden er miniaturen gegenereerd, maar deze worden nergens opgeslagen. Dit betekent dat elke keer dat een gebruiker de albumpagina bezoekt, de miniaturen worden geregenereerd. Er zijn twee manieren om dit probleem te verhelpen: door ze te genereren nadat het record is opgeslagen of door on-the-fly een generatie op te voeren.

    De eerste optie omvat het introduceren van een nieuwe kolom om de thumbnail op te slaan en de te wijzigen dragonfly_accessor methode. Maak en pas een nieuwe migratie toe:

    rails g migratie add_image_thumb_uid_to_albums image_thumb_uid: stringrails db: migreren

    Pas nu het model aan:

    modellen / album.rb

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

    Merk op dat nu de eerste oproep aan dragonfly_accessor stuurt een blok dat de thumbnail voor ons genereert en kopieert naar de image_thumb. Gebruik nu gewoon de image_thumb methode in uw weergaven:

    views / albums / _album.html.erb

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

    Deze oplossing is de eenvoudigste, maar wordt niet aanbevolen door de officiële documenten en, wat nog erger is, op het moment van schrijven werkt het niet met de retained_ * velden.

    Daarom laat ik je nog een andere optie zien: direct miniaturen genereren. Het gaat om het maken van een nieuw model en het aanpassen van het configuratiebestand van Dragonfly. Ten eerste, het model:

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

    De duimen tabel host uw miniaturen, maar ze worden op verzoek gegenereerd. Om dit mogelijk te maken, moeten we de. Herdefiniëren url methode binnen de Dragonfly-initialisatie:

    config / initialiseerders / dragonfly.rb

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

    Voeg nu een nieuw album toe en bezoek de hoofdpagina. De eerste keer dat u het doet, wordt de volgende uitvoer in de logboeken afgedrukt:

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

    Dit betekent in feite dat de thumbnail voor ons wordt aangemaakt door ImageMagick. Als u de pagina opnieuw laadt, wordt deze regel echter niet meer weergegeven, wat betekent dat de miniatuur in de cache is geplaatst! U kunt meer over deze functie lezen op de website van Dragonfly.

    Meer verwerking

    U kunt vrijwel elke bewerking van uw afbeeldingen uitvoeren nadat ze zijn geüpload. Dit kan worden gedaan binnen de after_assign Bel terug. Laten we bijvoorbeeld al onze afbeeldingen converteren naar een JPEG-indeling met 90% kwaliteit: 

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

    Er zijn veel meer acties die u kunt uitvoeren: roteer en snijd de afbeeldingen bij, codeer met een ander formaat, schrijf er tekst op, meng met andere afbeeldingen (bijvoorbeeld om een ​​watermerk te plaatsen), enz. Zie enkele andere voorbeelden. het gedeelte ImageMagick op de Dragonfly-website.

    Songs uploaden en beheren

    Natuurlijk, het grootste deel van onze muzikale site is liedjes, dus laten we ze nu toevoegen. Elk nummer heeft een titel en een muzikaal bestand en het hoort bij een album:

    rails g model Lied album: belong_to title: string track_uid: string rails db: migrate

    Sluit de Dragonfly-methoden aan, zoals we deden voor de Album model:

    modellen / song.rb

    dragonfly_accessor: track

    Vergeet niet om een heeft veel relatie:

    modellen / album.rb

    has_many: songs, afhankelijk:: destroy

    Voeg nieuwe routes toe. Een nummer bestaat altijd in het bereik van een album, dus ik zal ervoor zorgen dat deze routes worden genest:

    config / routes.rb

    middelen: albums doen bronnen: liedjes, alleen: [: nieuw,: maken] einde

    Maak een heel eenvoudige controller (nogmaals, vergeet niet om de spoor veld):

    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

    Toon de liedjes en een link om een ​​nieuwe toe te voegen:

    views / albums / show.html.erb

    <%= @album.title %>

    door <%= @album.singer %>

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

    Codeer het formulier:

    views / songs / new.html.erb

    Nummer toevoegen aan <%= @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 %>

    Voeg ten slotte de _song gedeeltelijke:

    views / songs / _song.html.erb

  • <%= audio_tag song.track.url, controls: true %> <%= song.title %>
  • Hier gebruik ik de HTML5 audio tag, wat niet werkt voor oudere browsers. Dus, als u dergelijke browsers wilt ondersteunen, gebruik dan een polyfill.

    Zoals je ziet, is het hele proces heel eenvoudig. Dragonfly maakt niet echt uit welk type bestand je wilt uploaden; alles wat je hoeft te doen is zorgen voor een dragonfly_accessor methode, voeg een juist veld toe, laat het toe en render een invoerlabel voor bestanden.

    Metadata opslaan

    Wanneer ik een afspeellijst open, verwacht ik wat aanvullende informatie over elk nummer te zien, zoals de duur of bitrate. Natuurlijk wordt deze info standaard niet overal opgeslagen, maar we kunnen dat vrij eenvoudig verhelpen. Met Dragonfly kunnen we aanvullende gegevens over elk geüploade bestand verstrekken en dit later ophalen met behulp van de meta methode.

    Dingen zijn echter een beetje ingewikkelder wanneer we met audio of video werken, omdat om hun metadata op te halen, een speciale gem streamio-ffmpeg nodig is. Deze edelsteen is op zijn beurt afhankelijk van FFmpeg, dus om door te gaan moet je het op je pc installeren.

    Toevoegen streamio-ffmpeg in de Gemfile:

    Gemfile

    gem 'streamio-ffmpeg'

    Installeer het:

    bundel installeren

    Nu kunnen we hetzelfde gebruiken after_assign terugbellen al gezien in de vorige secties:

    modellen / song.rb

    dragonfly_accessor: track doen 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

    Merk op dat ik hier gebruik maak van een pad methode, niet url, omdat we op dit moment met een tijdelijk bestand werken. Vervolgens extraheren we de duur van het nummer (het converteren naar minuten en seconden met voorloopnullen) en de bijbehorende bitrate (converteren naar kilobytes per seconde).

    Tenslotte, toon metadata in de weergave:

    views / songs / _song.html.erb

  • <%= audio_tag song.track.url, controls: true %> <%= song.title %> (<%= song.track.meta['duration'] %>, <%= song.track.meta['bitrate'] %>Kb / s)
  • Als u de inhoud controleert op de public / system / libel map (de standaardlocatie voor het hosten van de uploads), u zult er een paar opmerken .YML bestanden - ze slaan alle meta-informatie op in YAML-indeling.

    Implementeren naar Heroku

    Het laatste onderwerp dat we vandaag behandelen, is hoe u uw applicatie voorbereidt voordat u hem implementeert op het Heroku-cloudplatform. Het grootste probleem is dat Heroku je niet toestaat om aangepaste bestanden (zoals uploads) op te slaan, dus we moeten vertrouwen op een cloudopslagdienst zoals Amazon S3. Gelukkig kan Dragonfly er gemakkelijk mee worden geïntegreerd.

    Het enige dat u hoeft te doen is een nieuw account bij AWS registreren (als u het nog niet hebt), een gebruiker met toestemming voor toegang tot S3-buckets maken en het sleutelpaar van de gebruiker op een veilige locatie noteren. Je zou een root key-paar kunnen gebruiken, maar dit is echt niet aangeraden. Maak ten slotte een S3-bucket.

    Ga terug naar onze Rails-applicatie en laat een nieuw juweel zien:  

    Gemfile 

    group: production do gem 'dragonfly-s3_data_store' end

    Installeer het:

    bundel installeren

    Wijzig daarna de configuratie van Dragonfly om S3 te gebruiken in een productieomgeving:

    config / initialiseerders / dragonfly.rb

    als Rails.env.production? datastore: s3, bucket_name: ENV ['S3_BUCKET'], access_key_id: ENV ['S3_KEY'], secret_access_key: ENV ['S3_SECRET'], regio: ENV ['S3_REGION'], url_scheme: 'https' else datastore: bestand, root_path: Rails.root.join ('public / system / dragonfly', Rails.env), server_root: Rails.root.join ('public') end

    Voorzien ENV variabelen op Heroku, gebruik deze opdracht:

    heroku config: voeg SOME_KEY = SOME_VALUE toe

    Als u de integratie met S3 lokaal wilt testen, kunt u een edelsteenachtige dotenv-rails gebruiken om omgevingsvariabelen te beheren. Onthoud echter dat uw AWS-sleutelpaar mag niet openbaar worden getoond!

    Een ander klein probleem dat ik tegenkwam tijdens de implementatie in Heroku was de afwezigheid van FFmpeg. Het punt is dat wanneer een nieuwe Heroku-applicatie wordt gemaakt, deze een set services heeft die vaak worden gebruikt (bijvoorbeeld ImageMagick is standaard beschikbaar). Andere services kunnen worden geïnstalleerd als Heroku-add-ons of in de vorm van buildpacks. Om een ​​FFmpeg buildpack toe te voegen, voert u de volgende opdracht uit:

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

    Nu is alles klaar en kun je je muzikale toepassing delen met de wereld!

    Conclusie

    Dit was een lange reis, nietwaar? Vandaag hebben we Dragonfly besproken - een oplossing voor het uploaden van bestanden in Rails. We hebben de basisconfiguratie gezien, enkele configuratieopties, het genereren van miniaturen, het verwerken en het opslaan van metadata. We hebben Dragonfly ook geïntegreerd met de Amazon S3-service en onze applicatie voorbereid voor inzet op productie.

    Natuurlijk hebben we in dit artikel niet alle aspecten van Dragonfly besproken, dus zorg ervoor dat je op zijn officiële website bladert voor uitgebreide documentatie en handige voorbeelden. Als je nog andere vragen hebt of vastzit met een aantal codevoorbeelden, aarzel dan niet om contact met mij op te nemen.

    Bedankt dat je bij me bent gebleven en tot snel!