Téléchargement de fichiers avec Rails et Libellule

Il y a quelque temps, j'ai écrit un article sur le téléchargement de fichiers avec Rails et Shrine expliquant comment introduire une fonctionnalité de téléchargement de fichier dans votre application Rails à l'aide de la gemme Shrine. Il existe cependant un ensemble de solutions similaires, et l'un de mes préférés est Dragonfly, une solution de téléchargement facile à utiliser pour Rails and Rack créée par Mark Evans.. 

Nous avons couvert cette bibliothèque au début de l’année dernière mais, comme pour la plupart des logiciels, il est utile de consulter de temps en temps les bibliothèques pour voir ce qui a changé et comment nous pouvons l’utiliser dans notre application.

Dans cet article, je vais vous guider dans la configuration de Dragonfly et vous expliquer comment utiliser ses principales fonctionnalités. Vous allez apprendre à:

  • Intégrez Dragonfly dans votre application
  • Configurer les modèles pour qu'ils fonctionnent avec Dragonfly
  • Introduire un mécanisme de téléchargement de base
  • Introduire des validations
  • Générer des vignettes d'image
  • Effectuer un traitement de fichier
  • Stocker les métadonnées pour les fichiers téléchargés
  • Préparer une application pour le déploiement

Pour rendre les choses plus intéressantes, nous allons créer une petite application musicale. Il présentera des albums et des chansons associées pouvant être gérés et lus sur le site Web..

Le code source de cet article est disponible sur GitHub. Vous pouvez également consulter la démo de l'application.

Liste et gestion des albums

Pour commencer, créez une nouvelle application Rails sans la suite de tests par défaut:

rails nouveau UploadingWithDragonfly -T

Pour cet article, j'utiliserai Rails 5, mais la plupart des concepts décrits s'appliquent également aux versions antérieures..

Création du modèle, du contrôleur et des itinéraires

Notre petit site musical va contenir deux modèles: Album et Chanson. Pour l'instant, créons le premier avec les champs suivants:

  • Titre (chaîne) - contient le titre de l'album
  • chanteur (chaîne) l'interprète de l'album
  • image_uid (chaîne) -un champ spécial pour stocker l’image d’aperçu de l’album. Ce champ peut être nommé comme vous voulez, mais il doit contenir le _uid suffixe comme indiqué par la documentation Dragonfly.

Créez et appliquez la migration correspondante:

rails g model Titre de l'album: string singer: string image_uid: string rails db: migrate

Créons maintenant un contrôleur très générique pour gérer les albums avec toutes les actions par défaut:

albums_controller.rb

classe 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

Enfin, ajoutez les routes:

config / routes.rb

ressources: albums

Intégration de libellule

Il est temps que Dragonfly se mette à l'honneur. Tout d'abord, ajoutez la gemme dans le Gemfile:

Gemfile

bijou 'libellule'

Courir:

bundle install rails produisent une libellule

Cette dernière commande créera un initialiseur nommé libellule.rb avec la configuration par défaut. Nous allons le mettre de côté pour l'instant, mais vous pouvez lire diverses options sur le site officiel de Dragonfly..

La prochaine chose importante à faire est d’équiper notre modèle des méthodes de Dragonfly. Ceci est fait en utilisant le libellule_accesseur:

modèles / album.rb

libellule_accesseur: image

Notez que je dis ici :image-il est directement lié à la image_uid colonne que nous avons créée dans la section précédente. Si, par exemple, vous avez nommé votre colonne photo_uid, puis le libellule_accesseur méthode aurait besoin de recevoir :photo comme argument.

Si vous utilisez Rails 4 ou 5, une autre étape importante consiste à marquer le :image domaine (pas : image_uid!) comme autorisé dans le contrôleur:

albums_controller.rb

params.require (: album) .permit (: titre,: chanteur,: image)

C’est à peu près cela: nous sommes prêts à créer des vues et à commencer à télécharger nos fichiers.!

Créer des vues

Commencez avec la vue index:

views / albums / index.html.erb

Albums

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

Maintenant le partiel:

visionnements / albums / _album.html.erb

  • <%= image_tag(album.image.url, alt: album.title) if album.image_stored? %> <%= link_to album.title, album_path(album) %> par <%= album.singer %> | <%= link_to 'Edit', edit_album_path(album) %> | <%= link_to 'Remove', album_path(album), method: :delete, data: confirm: 'Are you sure?' %>
  • Il y a deux méthodes de libellule à noter ici:

    • album.image.url renvoie le chemin vers l'image.
    • album.image_stored? dit si l'enregistrement a un fichier téléchargé en place.

    Ajoutez maintenant les nouvelles pages et les pages d'édition:

    views / albums / new.html.erb

    Ajouter un album

    <%= render 'form' %>

    views / albums / edit.html.erb

    modifier <%= @album.title %>

    <%= render 'form' %>

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

    La forme n'a rien d'extraordinaire, mais une fois encore, notez que nous disons :image, ne pas : image_uid, lors du rendu de l'entrée du fichier.

    Maintenant, vous pouvez démarrer le serveur et tester la fonctionnalité de téléchargement!

    Supprimer des images

    Ainsi, les utilisateurs peuvent créer et éditer des albums, mais il y a un problème: ils n'ont aucun moyen de supprimer une image, seulement de la remplacer par une autre. Heureusement, il est très facile de résoudre ce problème en introduisant une case à cocher "Supprimer l'image": 

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

    Si l’album est associé à une image, elle est affichée et une case à cocher est affichée. Si cette case est cochée, l'image sera supprimée. Notez que si votre champ est nommé photo_uid, alors la méthode correspondante pour supprimer l'attachement sera Retirer photo. Simple, n'est ce pas?

    La seule autre chose à faire est de permettre au remove_image Attribuer dans votre contrôleur:

    albums_controller.rb

    params.require (: album) .permit (: titre,: chanteur,: image,: remove_image)

    Ajout de validations

    À ce stade, tout fonctionne bien, mais nous ne vérifions pas du tout la saisie de l'utilisateur, ce qui n'est pas particulièrement intéressant. Par conséquent, ajoutons des validations pour le modèle Album:

    modèles / album.rb

    valide: titre, présence: vrai valide: chanteur, présence: vrai valide: image, présence: vrai propriété_valide: largeur, de: image, dans: (0… 900)

    validates_property est la méthode Dragonfly qui peut vérifier divers aspects de votre pièce jointe: vous pouvez valider l’extension, le type MIME, la taille, etc. d’un fichier..

    Créons maintenant un partiel générique pour restituer les erreurs trouvées:

    views / shared / _errors.html.erb

    <% if object.errors.any? %> 

    Les erreurs suivantes ont été trouvées:

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

    Employer cette partielle dans le formulaire:

    visionnements / albums / _form.html.erb

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

    Style les champs avec des erreurs un peu pour les décrire visuellement:

    feuilles de style / application.scss

    .field_with_errors display: inline; étiquette couleur: rouge;  input background-color: lightpink; 

    Conserver une image entre les demandes

    Une fois les validations introduites, nous nous heurtons à un autre problème (scénario assez typique, non?): Si l’utilisateur a commis des erreurs en remplissant le formulaire, il devra à nouveau choisir le fichier après avoir cliqué sur le bouton Soumettre bouton.

    Dragonfly peut également vous aider à résoudre ce problème en utilisant un retenu_ * champ caché:

    visionnements / albums / _form.html.erb

    <%= f.hidden_field :retained_image %>

    N'oubliez pas de permettre également ce champ:

    albums_controller.rb

    params.require (: album) .permit (: titre,: chanteur,: image,: remove_image,: held_image)

    Maintenant, l'image sera persistée entre les demandes! Le seul petit problème, cependant, est que l'entrée de téléchargement de fichier affichera toujours le message "choisir un fichier", mais cela peut être corrigé avec un style et un tiret de JavaScript..

    Traitement des images

    Générer des vignettes

    Les images téléchargées par nos utilisateurs peuvent avoir des dimensions très différentes, ce qui peut (et aura probablement) un impact négatif sur la conception du site. Vous voudrez probablement réduire les images à des dimensions fixes, ce qui est bien sûr possible en utilisant le largeur et la taille modes. Ce n’est cependant pas une approche optimale: le navigateur devra tout de même télécharger les images en taille réelle puis les réduire..

    Une autre option (qui est généralement bien meilleure) consiste à générer des vignettes d’image avec certaines dimensions prédéfinies sur le serveur. C'est très simple à réaliser avec Dragonfly:

    visionnements / albums / _album.html.erb

  • <%= image_tag(album.image.thumb('250x250#').url, alt: album.title) if album.image_stored? %> <%#… %>
  • 250x250 est, bien sûr, les dimensions, alors que # est la géométrie qui signifie "redimensionner et rogner si nécessaire pour maintenir les proportions avec la gravité au centre". Vous pouvez trouver des informations sur d'autres géométries sur le site de Dragonfly.

    le pouce Cette méthode est optimisée par ImageMagick, une excellente solution pour créer et manipuler des images. Par conséquent, afin de voir la démo de travail localement, vous devez installer ImageMagick (toutes les principales plates-formes sont prises en charge).. 

    La prise en charge d'ImageMagick est activée par défaut dans l'initialiseur de Dragonfly:

    config / initializers / libellule.rb

    plugin: imagemagick

    Des vignettes sont maintenant générées, mais elles ne sont stockées nulle part. Cela signifie que chaque fois qu'un utilisateur visite la page d'albums, les vignettes seront régénérées. Il existe deux manières de résoudre ce problème: en les générant après l'enregistrement de l'enregistrement ou en effectuant une génération à la volée..

    La première option consiste à introduire une nouvelle colonne pour stocker la vignette et à peaufiner la libellule_accesseur méthode. Créez et appliquez une nouvelle migration:

    rails g migration add_image_thumb_uid_to_albums image_thumb_uid: chaîne rails db: migrate

    Maintenant modifiez le modèle:

    modèles / album.rb

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

    Notez que maintenant le premier appel à libellule_accesseur envoie un bloc qui génère réellement la vignette pour nous et le copie dans le image_thumb. Maintenant, utilisez simplement le image_thumb méthode dans vos vues:

    visionnements / albums / _album.html.erb

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

    Cette solution est la plus simple, mais elle n’est pas recommandée par la documentation officielle et, pire encore, au moment de la rédaction du présent document, elle ne fonctionnait pas avec la retenu_ * des champs.

    Par conséquent, laissez-moi vous montrer une autre option: générer des vignettes à la volée. Cela implique de créer un nouveau modèle et de peaufiner le fichier de configuration de Dragonfly. Tout d'abord, le modèle:

    rails g model Thumb uid: chaîne job: chaîne rake db: migrate

    le les pouces table hébergera vos vignettes, mais elles seront générées à la demande. Pour ce faire, nous devons redéfinir la url méthode à l'intérieur de l'initialiseur Dragonfly:

    config / initializers / libellule.rb

    Dragonfly.app.configure do define_url do | application, travail, opte | thumb = Thumb.find_by_job (job.signature) si 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

    Ajoutez maintenant un nouvel album et visitez la page racine. La première fois que vous le faites, le résultat suivant sera imprimé dans les journaux:

    DRAGONFLY: commande shell: "convert" "chemin_proche / public / système / libellule / développement / 2017/02/08 / 3z5p5nvbmx_Folder.jpg" "-resize" "250x250 ^^" "-gravité" "Centre" "-" " 250x250 + 0 + 0 "" + repage "" some_path / 20170208-1692-1xrqzc9.jpg "

    Cela signifie effectivement que la vignette est générée pour nous par ImageMagick. Si vous rechargez la page, cependant, cette ligne n'apparaîtra plus, ce qui signifie que la vignette a été mise en cache! Vous pouvez lire un peu plus sur cette fonctionnalité sur le site de Dragonfly.

    Plus de traitement

    Vous pouvez pratiquement manipuler vos images après leur téléchargement. Cela peut être fait à l'intérieur du after_assign rappeler. Convertissons par exemple toutes nos images au format JPEG avec une qualité de 90%: 

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

    Vous pouvez effectuer de nombreuses autres actions: faire pivoter et rogner les images, encoder avec un format différent, écrire du texte dessus, les mélanger avec d'autres images (par exemple, pour placer un filigrane), etc. Pour voir d'autres exemples, reportez-vous à la section ImageMagick sur le site Web de Dragonfly.

    Téléchargement et gestion de chansons

    Bien sûr, la partie principale de notre site musical est constituée de chansons, ajoutons-les maintenant. Chaque chanson a un titre et un fichier musical, et elle appartient à un album:

    rails g model Album de morceaux: appartient à titre: chaine track_uid: string rails db: migrate

    Connectez les méthodes de libellule, comme nous l'avons fait pour le Album modèle:

    modèles / song.rb

    libellule_accesseur: piste

    N'oubliez pas d'établir un a beaucoup relation:

    modèles / album.rb

    has_many: chansons, dépendant:: détruire

    Ajouter de nouveaux itinéraires. Une chanson existe toujours dans le cadre d'un album, je vais donc imbriquer ces routes:

    config / routes.rb

    ressources: les albums font des ressources: chansons, seulement: [: nouveau,: créer] fin

    Créez un contrôleur très simple (encore une fois, n'oubliez pas d'autoriser le Piste champ):

    songs_controller.rb

    classe 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

    Affichez les chansons et un lien pour en ajouter une nouvelle:

    views / albums / show.html.erb

    <%= @album.title %>

    par <%= @album.singer %>

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

    Codez le formulaire:

    views / songs / new.html.erb

    Ajouter une chanson à <%= @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 %>

    Enfin, ajoutez le _chanson partiel:

    views / songs / _song.html.erb

  • <%= audio_tag song.track.url, controls: true %> <%= song.title %>
  • Ici j'utilise le HTML5 l'audio balise, qui ne fonctionnera pas pour les anciens navigateurs. Donc, si vous souhaitez supporter de tels navigateurs, utilisez un polyfill.

    Comme vous le voyez, l'ensemble du processus est très simple. Dragonfly ne se soucie pas vraiment du type de fichier que vous souhaitez télécharger; tout ce que vous devez faire est de fournir un libellule_accesseur méthode, ajouter un champ approprié, l'autoriser et restituer une balise d'entrée de fichier.

    Stockage des métadonnées

    Lorsque j'ouvre une liste de lecture, je m'attends à voir des informations supplémentaires sur chaque chanson, telles que sa durée ou son débit. Bien sûr, par défaut, ces informations ne sont stockées nulle part, mais nous pouvons y remédier assez facilement. Dragonfly nous permet de fournir des données supplémentaires sur chaque fichier téléchargé et de les récupérer plus tard en utilisant le méta méthode.

    Les choses sont toutefois un peu plus complexes lorsque nous travaillons avec de l'audio ou de la vidéo, car pour extraire leurs métadonnées, une gem streamio-ffmpeg spéciale est nécessaire. Ce bijou, à son tour, repose sur FFmpeg, vous devez donc l'installer sur votre PC..

    Ajouter streamio-ffmpeg dans le Gemfile:

    Gemfile

    gem 'streamio-ffmpeg'

    Installez-le:

    installation groupée

    Maintenant nous pouvons employer le même after_assign rappel déjà vu dans les sections précédentes:

    modèles / song.rb

    dragonfly_accessor: track do after_assign do | a | chanson = FFMPEG :: Movie.new (a.path) mm, ss = chanson.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

    Notez que je me sers ici chemin méthode, pas url, car à ce stade, nous travaillons avec un fichier temporaire. Ensuite, nous extrayons simplement la durée de la chanson (conversion en minutes et secondes avec des zéros non significatifs) et son débit (conversion en kilo-octets par seconde).

    Enfin, affichez les métadonnées dans la vue:

    views / songs / _song.html.erb

  • <%= audio_tag song.track.url, controls: true %> <%= song.title %> (<%= song.track.meta['duration'] %>, <%= song.track.meta['bitrate'] %>Kb / s)
  • Si vous vérifiez le contenu sur le public / système / libellule dossier (l’emplacement par défaut pour héberger les téléchargements), vous remarquerez quelques .yml fichiers-ils stockent toutes les méta-informations au format YAML.

    Déploiement à Heroku

    Le dernier sujet que nous aborderons aujourd'hui concerne la préparation de votre application avant son déploiement sur la plate-forme cloud Heroku. Le problème principal est que Heroku ne vous autorise pas à stocker des fichiers personnalisés (tels que les téléchargements). Nous devons donc nous appuyer sur un service de stockage en nuage comme Amazon S3. Heureusement, Dragonfly peut être intégré facilement.

    Tout ce que vous avez à faire est d’enregistrer un nouveau compte sur AWS (si vous ne l’avez pas déjà), de créer un utilisateur autorisé à accéder aux compartiments S3 et d’écrire la paire de clés de l’utilisateur dans un emplacement sûr. Vous pouvez utiliser une paire de clés racine, mais c’est vraiment non recommandé. Enfin, créez un compartiment S3.

    Pour revenir à notre application Rails, déposez un nouveau joyau:  

    Gemfile 

    groupe: production do gem 'libellule-s3_data_store' fin

    Installez-le:

    installation groupée

    Puis ajustez la configuration de Dragonfly pour utiliser S3 dans un environnement de production:

    config / initializers / libellule.rb

    si Rails.env.production? magasin de données: s3, bucket_name: ENV ['S3_BUCKET'], clé_accès_id: ENV ['S3_KEY'], secret_access_key: ENV ['S3_SECRET'], région: ENV ['S3_REGION'], url_scheme: 'https', autre date, chemin_racine: Rails.root.join ('public / system / libellule', Rails.env), racine du serveur: Rails.root.join ('public') end

    Fournir ENV variables sur Heroku, utilisez cette commande:

    heroku config: add SOME_KEY = SOME_VALUE

    Si vous souhaitez tester l'intégration avec S3 localement, vous pouvez utiliser une gemme telle que dotenv-rails pour gérer les variables d'environnement. Rappelez-vous cependant que votre paire de clés AWS ne doit pas être exposé publiquement!

    L’absence de FFmpeg est un autre petit problème que j’ai rencontré lors de mon déploiement à Heroku. Le fait est que lors de la création d’une nouvelle application Heroku, elle dispose d’un ensemble de services couramment utilisés (par exemple, ImageMagick est disponible par défaut). D'autres services peuvent être installés sous forme d'additifs Heroku ou sous la forme de packs de construction. Pour ajouter un buildpack FFmpeg, exécutez la commande suivante:

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

    Maintenant tout est prêt et vous pouvez partager votre application musicale avec le monde entier.!

    Conclusion

    Ce fut un long voyage, n'est-ce pas? Aujourd'hui, nous avons discuté de Dragonfly, une solution de téléchargement de fichiers dans Rails. Nous avons vu sa configuration de base, certaines options de configuration, la génération, le traitement et l’enregistrement de métadonnées. De plus, nous avons intégré Dragonfly au service Amazon S3 et préparé notre application en vue de son déploiement en production..

    Bien sûr, nous n’avons pas abordé tous les aspects de Dragonfly dans cet article, assurez-vous donc de parcourir son site officiel pour trouver une documentation complète et des exemples utiles. Si vous avez d'autres questions ou si vous avez des exemples de code, n'hésitez pas à me contacter..

    Merci de rester avec moi et à bientôt!