Subiendo archivos con Rails y Dragonfly

Hace algún tiempo escribí un artículo de Carga de archivos con Rails y Shrine que explicaba cómo introducir una función de carga de archivos en su aplicación Rails con la ayuda de la gema Shrine. Sin embargo, hay un montón de soluciones similares disponibles, y una de mis favoritas es Dragonfly, una solución de carga fácil de usar para Rails y Rack creada por Mark Evans.. 

Cubrimos esta biblioteca a principios del año pasado, pero, al igual que con la mayoría de los programas, es útil echar un vistazo a las bibliotecas de vez en cuando para ver qué ha cambiado y cómo podemos emplearlo en nuestra aplicación..

En este artículo, lo guiaré a través de la configuración de Dragonfly y le explicaré cómo utilizar sus funciones principales. Aprenderás cómo:

  • Integra Dragonfly en tu aplicación
  • Configurar modelos para trabajar con Dragonfly.
  • Introducir un mecanismo básico de carga.
  • Introducir validaciones
  • Generar miniaturas de imágenes
  • Realizar procesamiento de archivos
  • Almacenar metadatos para archivos subidos
  • Preparar una aplicación para el despliegue.

Para hacer las cosas más interesantes, vamos a crear una pequeña aplicación musical. Presentará álbumes y canciones asociadas que se pueden administrar y reproducir en el sitio web..

El código fuente de este artículo está disponible en GitHub. También puedes consultar la demo de trabajo de la aplicación..

Listado y gestión de álbumes

Para comenzar, cree una nueva aplicación de Rails sin el conjunto de pruebas predeterminado:

Rieles nuevos UcomingWithDragonfly -T

Para este artículo usaré Rails 5, pero la mayoría de los conceptos descritos también se aplican a versiones anteriores..

Creación del modelo, controlador y rutas

Nuestro pequeño sitio musical va a contener dos modelos: Álbum y Canción. Por ahora, vamos a crear el primero con los siguientes campos:

  • título (cuerda) -contiene el título del álbum
  • cantante (cuerda) ejecutante de -album
  • image_uid (cuerda) -un campo especial para almacenar la imagen de vista previa del álbum. Este campo puede ser nombrado como quieras, pero debe contener el _uid sufijo como lo indica la documentación de Dragonfly.

Crea y aplica la migración correspondiente:

rieles modelo g Título del álbum: string singer: string image_uid: string rails db: migrate

Ahora vamos a crear un controlador muy genérico para administrar álbumes con todas las acciones predeterminadas:

albums_controller.rb

clase 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

Por último, añadir las rutas:

config / route.rb

recursos: álbumes

Integrando libélula

Es hora de que Dragonfly entre en el centro de atención. Primero, agregue la gema en el Gemfile:

Gemfile

gema 'libélula'

Correr:

Paquetes de instalación de paquetes generan libélula

El último comando creará un inicializador llamado libélula.rb con la configuración por defecto. Lo dejaremos de lado por ahora, pero puede leer sobre varias opciones en el sitio web oficial de Dragonfly.

Lo siguiente que debemos hacer es equipar nuestro modelo con los métodos de Dragonfly. Esto se hace usando el dragonfly_accessor:

modelos / album.rb

dragonfly_accessor: imagen

Tenga en cuenta que aquí estoy diciendo :imagen-se relaciona directamente con el image_uid Columna que creamos en la sección anterior. Si, por ejemplo, nombraste tu columna foto_uid, entonces el dragonfly_accessor método necesitaría recibir :foto como argumento.

Si está utilizando Rails 4 o 5, otro paso importante es marcar el :imagen campo (no : image_uid!) según lo permitido en el controlador:

albums_controller.rb

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

Esto es prácticamente todo: estamos listos para crear vistas y comenzar a cargar nuestros archivos.!

Creando Vistas

Comience con la vista de índice:

views / albums / index.html.erb

Los álbumes

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

Ahora el parcial:

vistas / álbumes / _album.html.erb

  • <%= image_tag(album.image.url, alt: album.title) if album.image_stored? %> <%= link_to album.title, album_path(album) %> por <%= album.singer %> | <%= link_to 'Edit', edit_album_path(album) %> | <%= link_to 'Remove', album_path(album), method: :delete, data: confirm: 'Are you sure?' %>
  • Hay dos métodos de libélula para tener en cuenta aquí:

    • album.image.url devuelve el camino a la imagen.
    • album.image_stored? dice si el registro tiene un archivo cargado en su lugar.

    Ahora agregue las nuevas páginas y editar:

    vistas / álbumes / nuevo.html.erb

    Agregar album

    <%= render 'form' %>

    vistas / álbumes / edit.html.erb

    Editar <%= @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 %>

    La forma no es nada especial, pero una vez más note que estamos diciendo :imagen, no : image_uid, al renderizar la entrada del archivo.

    Ahora puedes arrancar el servidor y probar la función de carga.!

    Eliminando imagenes

    Así que los usuarios pueden crear y editar álbumes, pero hay un problema: no tienen forma de eliminar una imagen, solo de reemplazarla con otra. Afortunadamente, esto es muy fácil de solucionar al introducir una casilla de verificación "eliminar imagen": 

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

    Si el álbum tiene una imagen asociada, lo mostramos y representamos una casilla de verificación. Si esta casilla de verificación está activada, la imagen se eliminará. Tenga en cuenta que si su campo es nombrado foto_uid, entonces el método correspondiente para eliminar el archivo adjunto será remove_photo. Simple no es?

    Lo único que hay que hacer es permitir la quita la imagen atributo en su controlador:

    albums_controller.rb

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

    Agregando Validaciones

    En esta etapa, todo funciona bien, pero no estamos revisando la entrada del usuario, lo que no es particularmente bueno. Por lo tanto, vamos a agregar validaciones para el modelo de álbum:

    modelos / album.rb

    valida: título, presencia: verdadero valida: cantante, presencia: verdadero valida: imagen, presencia: verdadero valida_propiedad: ancho, de:: imagen, en: (0… 900)

    validates_property es el método Dragonfly que puede verificar varios aspectos de su archivo adjunto: puede validar la extensión de un archivo, el tipo MIME, el tamaño, etc..

    Ahora vamos a crear un parcial genérico para representar los errores encontrados:

    views / shared / _errors.html.erb

    <% if object.errors.any? %> 

    Se encontraron los siguientes errores:

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

    Emplea este parcial dentro del formulario:

    views / albums / _form.html.erb

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

    Diseñe los campos con errores un poco para representarlos visualmente:

    stylesheets / application.scss

    .field_with_errors display: inline; etiqueta color: rojo;  entrada background-color: lightpink; 

    Retener una imagen entre solicitudes

    Una vez introducidas las validaciones, nos encontramos con otro problema (un escenario bastante típico, ¿eh?): Si el usuario ha cometido errores al completar el formulario, tendrá que elegir el archivo nuevamente después de hacer clic en Enviar botón.

    Dragonfly puede ayudarte a resolver este problema también usando un retenido_ * campo escondido:

    views / albums / _form.html.erb

    <%= f.hidden_field :retained_image %>

    No te olvides de permitir este campo también:

    albums_controller.rb

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

    ¡Ahora la imagen será persistida entre peticiones! Sin embargo, el único pequeño problema es que la entrada de carga del archivo aún mostrará el mensaje "elegir un archivo", pero esto puede solucionarse con un poco de estilo y un guión de JavaScript.

    Procesamiento de imágenes

    Generando miniaturas

    Las imágenes cargadas por nuestros usuarios pueden tener dimensiones muy diferentes, lo que puede (y probablemente causará) un impacto negativo en el diseño del sitio web. Probablemente le gustaría escalar las imágenes a algunas dimensiones fijas, y por supuesto esto es posible utilizando el anchura y altura estilos Sin embargo, este no es un enfoque óptimo: el navegador todavía necesitará descargar imágenes a tamaño completo y luego reducirlas..

    Otra opción (que suele ser mucho mejor) es generar miniaturas de imágenes con algunas dimensiones predefinidas en el servidor. Esto es realmente sencillo de lograr con Dragonfly:

    vistas / álbumes / _album.html.erb

  • <%= image_tag(album.image.thumb('250x250#').url, alt: album.title) if album.image_stored? %> <%#… %>
  • 250x250 es, por supuesto, las dimensiones, mientras que # es la geometría que significa "cambiar el tamaño y recortar si es necesario para mantener la relación de aspecto con la gravedad central". Puede encontrar información sobre otras geometrías en el sitio web de Dragonfly.

    los pulgar El método es impulsado por ImageMagick, una excelente solución para crear y manipular imágenes. Por lo tanto, para ver la demostración de trabajo localmente, deberá instalar ImageMagick (todas las plataformas principales son compatibles). 

    El soporte para ImageMagick está habilitado de forma predeterminada dentro del inicializador de Dragonfly:

    config / initializers / dragonfly.rb

    plugin: imagemagick

    Ahora se generan miniaturas, pero no se almacenan en ninguna parte. Esto significa que cada vez que un usuario visita la página de álbumes, las miniaturas se regenerarán. Hay dos formas de superar este problema: generándolas después de guardar el registro o realizando la generación sobre la marcha.

    La primera opción consiste en introducir una nueva columna para almacenar la miniatura y ajustar la dragonfly_accessor método. Crea y aplica una nueva migración:

    rails g migration add_image_thumb_uid_to_albums image_thumb_uid: string rails db: migrate

    Ahora modifica el modelo:

    modelos / album.rb

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

    Tenga en cuenta que ahora la primera llamada a dragonfly_accessor envía un bloque que realmente genera la miniatura para nosotros y la copia en el image_thumb. Ahora solo usa el image_thumb método en sus puntos de vista:

    vistas / álbumes / _album.html.erb

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

    Esta solución es la más sencilla, pero no está recomendada por los documentos oficiales y, lo que es peor, en el momento de la redacción no funciona con el retenido_ * campos.

    Por lo tanto, déjame mostrarte otra opción: generar miniaturas sobre la marcha. Implica crear un nuevo modelo y ajustar el archivo de configuración de Dragonfly. Primero, el modelo:

    rieles modelo g Pulgar uid: trabajo de cadena: cadena rastrillo db: migrar

    los pulgares La tabla alojará sus miniaturas, pero se generarán a pedido. Para que esto suceda, necesitamos redefinir el url Método dentro del inicializador Dragonfly:

    config / initializers / dragonfly.rb

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

    Ahora agrega un nuevo álbum y visita la página raíz. La primera vez que lo haga, se imprimirá la siguiente salida en los registros:

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

    Esto efectivamente significa que ImageMagick nos genera la miniatura. Sin embargo, si vuelve a cargar la página, esta línea no aparecerá más, lo que significa que la miniatura se guardó en caché. Puedes leer un poco más sobre esta función en el sitio web de Dragonfly.

    Más procesamiento

    Puede realizar virtualmente cualquier manipulación de sus imágenes después de que se hayan cargado. Esto se puede hacer dentro de la after_assign llamar de vuelta. Por ejemplo, vamos a convertir todas nuestras imágenes a formato JPEG con una calidad del 90%: 

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

    Puede realizar muchas más acciones: rotar y recortar las imágenes, codificar con un formato diferente, escribir texto en ellas, mezclarlas con otras imágenes (por ejemplo, para colocar una marca de agua), etc. Para ver otros ejemplos, consulte La sección de ImageMagick en el sitio web de Dragonfly.

    Cargar y gestionar canciones

    Por supuesto, la parte principal de nuestro sitio musical son las canciones, así que vamos a agregarlas ahora. Cada canción tiene un título y un archivo musical, y pertenece a un álbum:

    rieles modelo g Álbum de canciones: belongs_to título: string track_uid: string rails db: migrate

    Conecta los métodos de Dragonfly, como hicimos para el Álbum modelo:

    modelos / song.rb

    dragonfly_accessor: track

    No te olvides de establecer una tiene muchos relación:

    modelos / album.rb

    has_many: canciones, dependiente:: destroy

    Añadir nuevas rutas. Siempre existe una canción en el alcance de un álbum, así que haré estas rutas anidadas:

    config / route.rb

    recursos: los álbumes hacen recursos: canciones, solo: [: nuevo,: crear] fin

    Cree un controlador muy simple (una vez más, no olvide permitir el pista campo):

    songs_controller.rb

    clase 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

    Muestra las canciones y un enlace para agregar una nueva:

    vistas / álbumes / show.html.erb

    <%= @album.title %>

    por <%= @album.singer %>

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

    Codifique el formulario:

    vistas / canciones / new.html.erb

    Añadir canción 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 %>

    Por último, agregue el _canción parcial:

    vistas / canciones / _song.html.erb

  • <%= audio_tag song.track.url, controls: true %> <%= song.title %>
  • Aquí estoy usando el HTML5 audio etiqueta, que no funcionará para los navegadores más antiguos. Por lo tanto, si su objetivo es admitir dichos navegadores, utilice un polyfill.

    Como ves, todo el proceso es muy sencillo. A Dragonfly no le importa realmente qué tipo de archivo deseas subir; Todo lo que necesitas hacer es proporcionar una dragonfly_accessor Método, agregue un campo adecuado, permítalo y genere una etiqueta de entrada de archivo.

    Almacenamiento de metadatos

    Cuando abro una lista de reproducción, espero ver información adicional sobre cada canción, como su duración o tasa de bits. Por supuesto, de forma predeterminada, esta información no se almacena en ningún lugar, pero podemos solucionarlo con bastante facilidad. Dragonfly nos permite proporcionar datos adicionales sobre cada archivo cargado y recuperarlos más tarde mediante el uso de meta método.

    Sin embargo, las cosas son un poco más complejas cuando trabajamos con audio o video, porque para obtener sus metadatos, se necesita una gema especial streamio-ffmpeg. Esta gema, a su vez, se basa en FFmpeg, por lo que para poder proceder, deberá instalarla en su PC..

    Añadir streamio-ffmpeg en el Gemfile:

    Gemfile

    gema 'streamio-ffmpeg'

    Instalarlo:

    instalación de paquete

    Ahora podemos emplear el mismo. after_assign Callback ya visto en las secciones anteriores:

    modelos / 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

    Tenga en cuenta que aquí estoy usando un camino método, no url, Porque en este punto estamos trabajando con un archivo temporal. A continuación, simplemente extraemos la duración de la canción (convirtiéndola a minutos y segundos con ceros iniciales) y su tasa de bits (convirtiéndola a kilobytes por segundo).

    Por último, mostrar los metadatos en la vista:

    vistas / canciones / _song.html.erb

  • <%= audio_tag song.track.url, controls: true %> <%= song.title %> (<%= song.track.meta['duration'] %>, <%= song.track.meta['bitrate'] %>Kb / s)
  • Si revisa los contenidos en el public / system / dragonfly carpeta (la ubicación predeterminada para alojar las cargas), anotará algunas .yml Archivos: están almacenando toda la metainformación en formato YAML..

    Desplegando a Heroku

    El último tema que cubriremos hoy es cómo preparar su aplicación antes de implementarla en la plataforma en la nube de Heroku. El principal problema es que Heroku no le permite almacenar archivos personalizados (como cargas), por lo que debemos confiar en un servicio de almacenamiento en la nube como Amazon S3. Por suerte, Dragonfly se puede integrar fácilmente con él..

    Todo lo que debe hacer es registrar una nueva cuenta en AWS (si aún no la tiene), cree un usuario con permiso para acceder a los depósitos de S3 y anote el par de claves del usuario en una ubicación segura. Puede usar un par de claves raíz, pero esto es realmente no recomendado. Por último, crea un cubo S3.

    Volviendo a nuestra aplicación Rails, ingresa una nueva gema:  

    Gemfile 

    grupo: producción do gema 'dragonfly-s3_data_store' final

    Instalarlo:

    instalación de paquete

    Luego, modifique la configuración de Dragonfly para utilizar S3 en un entorno de producción:

    config / initializers / dragonfly.rb

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

    Para proveer ENV variables en Heroku, usa este comando:

    heroku config: agregar SOME_KEY = SOME_VALUE

    Si desea probar la integración con S3 localmente, puede usar una gema como dotenv-rails para administrar las variables de entorno. Recuerde, sin embargo, que su par de claves AWS no debe ser expuesto públicamente!

    Otro problema pequeño con el que me he topado mientras implementaba en Heroku era la ausencia de FFmpeg. El problema es que cuando se crea una nueva aplicación de Heroku, tiene un conjunto de servicios que se utilizan comúnmente (por ejemplo, ImageMagick está disponible de forma predeterminada). Otros servicios se pueden instalar como complementos de Heroku o en forma de paquetes de compilación. Para agregar un buildpack FFmpeg, ejecute el siguiente comando:

    paquetes de compilación de heroku: agregue https://github.com/HYPERHYPER/heroku-buildpack-ffmpeg.git

    Ahora todo está listo, y puedes compartir tu aplicación musical con el mundo.!

    Conclusión

    Este fue un largo viaje, ¿no? Hoy hemos discutido Dragonfly, una solución para cargar archivos en Rails. Hemos visto su configuración básica, algunas opciones de configuración, generación de miniaturas, procesamiento y almacenamiento de metadatos. Además, hemos integrado Dragonfly con el servicio Amazon S3 y hemos preparado nuestra aplicación para la implementación en producción.

    Por supuesto, no hemos discutido todos los aspectos de Dragonfly en este artículo, así que asegúrese de navegar por su sitio web oficial para encontrar documentación extensa y ejemplos útiles. Si tiene alguna otra pregunta o está atascado con algunos ejemplos de código, no dude en contactarme.

    Gracias por estar conmigo y hasta pronto.!