Subiendo archivos con Rails y Shrine

Hay muchas gemas para subir archivos, como CarrierWave, Paperclip y Dragonfly, por nombrar algunas. Todos tienen sus detalles, y probablemente ya hayas usado al menos una de estas gemas..

Hoy, sin embargo, quiero presentar una solución relativamente nueva, pero muy interesante llamada Shrine, creada por Janko Marohnić. A diferencia de otras gemas similares, tiene un enfoque modular, lo que significa que cada característica se presenta como un módulo (o enchufar en la terminología del santuario). ¿Quieres apoyar validaciones? Añadir un plugin. ¿Desea hacer algún procesamiento de archivos? Añadir un plugin! Me encanta este enfoque, ya que le permite controlar fácilmente qué funciones estarán disponibles para cada modelo..

En este artículo voy a mostrarte cómo:

  • Integrar Shrine en una aplicación de Rails.
  • configurarlo (globalmente y por cargador)
  • añade la posibilidad de subir archivos
  • procesar archivos
  • agregar reglas de validación
  • almacene metadatos adicionales y emplee almacenamiento en la nube de archivos con Amazon S3

El código fuente de este artículo está disponible en GitHub..

La demo de trabajo se puede encontrar aquí..

Santuario integrador

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

rieles nuevo FileGuru -T

Usaré Rails 5 para esta demostración, pero la mayoría de los conceptos se aplican también a las versiones 3 y 4.

Deja caer la gema del Santuario en tu Gemfile:

gema "santuario"

Entonces corre:

instalación de paquete

Ahora necesitaremos un modelo al que voy a llamar. Foto. Shrine almacena toda la información relacionada con el archivo en una columna de texto especial que termina con un _datos sufijo. Crea y aplica la migración correspondiente:

rieles modelo g Título de la foto: cadena image_data: texto rieles db: migrate

Tenga en cuenta que para versiones anteriores de Rails, el último comando debe ser:

rastrillo db: migrar

Las opciones de configuración para Shrine se pueden configurar globalmente y por modelo. La configuración global se realiza, por supuesto, dentro del archivo de inicialización. Allí voy a conectar los archivos necesarios y plugins. Los complementos se utilizan en Shrine para extraer piezas de funcionalidad en módulos separados, lo que le brinda un control total de todas las funciones disponibles. Por ejemplo, hay complementos para validación, procesamiento de imágenes, archivos adjuntos de almacenamiento en caché y más.

Por ahora, agreguemos dos complementos: uno para admitir ActiveRecord y otro para configurar el registro. Van a ser incluidos globalmente. Además, configure el almacenamiento del sistema de archivos:

config / initializers / shrine.rb

require "shrine" require "shrine / storage / file_system" Shrine.plugin: activerecord Shrine.plugin: registro, registrador: Rails.logger Shrine.storages = cache: Shrine :: Storage :: FileSystem.new ("public", prefijo : "uploads / cache"), store: Shrine :: Storage :: FileSystem.new ("public", prefijo: "uploads / store"),

El registrador simplemente emitirá cierta información de depuración dentro de la consola para usted y le dirá cuánto tiempo se gastó para procesar un archivo. Esto puede ser útil.

2015-10-09T20: 06: 06.676Z # 25602: STORE [cache] ImageUploader [: avatar] User [29543] 1 archivo (0.1s) 2015-10-09T20: 06: 06.854Z # 25602: PROCESS [tienda]: ImageUploader [: avatar] User [29543] 1-3 archivos (0.22s) 2015-10-09T20: 06: 07.133Z # 25602: ELIMINAR [destruido]: ImageUploader [: avatar] User [29543] 3 archivos (0.07s)

Todos los archivos subidos serán almacenados dentro del public / uploads directorio. No quiero rastrear estos archivos en Git, así que excluya esta carpeta:

.gitignore

public / uploads

Ahora cree una clase especial de "cargador" que alojará configuraciones específicas del modelo. Por ahora, esta clase va a estar vacía:

modelos / image_uploader.rb

clase ImageUploader < Shrine end

Por último, incluir esta clase dentro del Foto modelo:

modelos / foto.rb

incluir ImageUploader [: image]

[:imagen] agrega un atributo virtual que se usará al construir un formulario. La línea anterior puede ser reescrita como:

 incluya ImageUploader.attachment (: image) # o incluya ImageUploader :: Attachment.new (: image) 

¡Bonito! Ahora el modelo está equipado con la funcionalidad de Shrine, y podemos continuar con el siguiente paso..

Controlador, Vistas y Rutas

Para los fines de esta demostración, solo necesitaremos un controlador para administrar las fotos. los índice La página servirá como raíz:

pages_controller.rb

clase PhotosController < ApplicationController def index @photos = Photo.all end end

La vista:

vistas / fotos / index.html.erb

Las fotos

<%= link_to 'Add Photo', new_photo_path %> <%= render @photos %>

Para hacer el @fotos array, se requiere un parcial:

vistas / fotos / _photo.html.erb

<% if photo.image_data? %> <%= image_tag photo.image_url %> <% end %>

<%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %>

datos de imagen? es un método presentado por ActiveRecord que verifica si un registro tiene una imagen.

URL de la imagen es un método Shrine que simplemente devuelve una ruta a la imagen original. Por supuesto, es mucho mejor mostrar una miniatura pequeña, pero nos encargaremos de eso más adelante..

Añade todas las rutas necesarias:

config / route.rb

 recursos: fotos, solo: [: nuevo,: crear,: índice,: editar,: actualizar] raíz 'fotos # índice'

Esto es todo, el trabajo está hecho y podemos continuar con la parte interesante.!

Cargando archivos

En esta sección, le mostraré cómo agregar la funcionalidad para cargar archivos en realidad. Las acciones del controlador son muy simples:

photos_controller.rb

def new @photo = Photo.new end def create @photo = Photo.new (photo_params) si @ photo.save flash [: success] = 'Photo added!' redirect_to photos_path else render 'new' end end

El único problema es que para parámetros fuertes debes permitir el imagen atributo virtual, no el datos de imagen.

photos_controller.rb

privado def photo_params params.require (: photo) .permit (: title,: image) end

Crear el nuevo ver:

vistas / fotos / nuevo.html.erb

Añadir foto

<%= render 'form' %>

El parcial del formulario también es trivial:

vistas / fotos / _form.html.erb

<%= form_for @photo do |f| %> <%= render "shared/errors", object: @photo %> <%= f.label :title %> <%= f.text_field :title %> <%= f.label :image %> <%= f.file_field :image %> <%= f.submit %> <% end %>

Una vez más, note que estamos usando el imagen atributo, no el datos de imagen.

Por último, agregue otro parcial para mostrar errores:

views / shared / _errors.html.erb

<% if object.errors.any? %> 

Se encontraron los siguientes errores:

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

Esto es prácticamente todo: puedes empezar a cargar imágenes ahora mismo..

Validaciones

Por supuesto, se debe hacer mucho más para completar la aplicación de demostración. El principal problema es que los usuarios pueden cargar absolutamente cualquier tipo de archivo con cualquier tamaño, lo que no es particularmente bueno. Por lo tanto, agregue otro plugin para soportar validaciones:

config / inititalizers / shrine.rb

Shrine.plugin: validation_helpers

Configurar la lógica de validación para el ImageUploader:

modelos / image_uploader.rb

Attacher.validate do validate_max_size 1.megabyte, mensaje: "es demasiado grande (el máximo es 1 MB)" validate_mime_type_inclusion ['image / jpg', 'image / jpeg', 'image / png'] end

Solo permito que se carguen imágenes JPG y PNG de menos de 1 MB. Ajusta estas reglas como mejor te parezca..

Tipos mime

Otra cosa importante a tener en cuenta es que, de forma predeterminada, Shrine determinará el tipo MIME de un archivo utilizando el encabezado HTTP de tipo de contenido. Este encabezado lo pasa el navegador y se establece solo en función de la extensión del archivo, lo que no siempre es deseable.

Si desea determinar el tipo MIME en función del contenido del archivo, utilice un complemento llamado determine_mime_type. Lo incluiré dentro de la clase del cargador, ya que otros modelos pueden no requerir esta funcionalidad:

modelos / image_uploader.rb

plugin: determinar_mime_type

Este complemento va a utilizar la utilidad de archivo de Linux por defecto.

Imágenes adjuntas de caching

Actualmente, cuando un usuario envía un formulario con datos incorrectos, el formulario se mostrará nuevamente con los errores presentados anteriormente. El problema, sin embargo, es que la imagen adjunta se perderá y el usuario deberá seleccionarla una vez más. Esto es muy fácil de solucionar utilizando otro complemento llamado cached_attachment_data:

modelos / image_uploader.rb

plugin: cached_attachment_data

Ahora simplemente agregue un campo oculto en su formulario.

vistas / fotos / _form.html.erb

<%= f.hidden_field :image, value: @photo.cached_image_data %> <%= f.label :image %> <%= f.file_field :image %>

Editando una foto

Ahora las imágenes se pueden cargar, pero no hay forma de editarlas, así que vamos a arreglarlas de inmediato. Las acciones del controlador correspondiente son algo triviales:

photos_controller.rb

def edit @photo = Photo.find (params [: id]) end def update @photo = Photo.find (params [: id]) si @ photo.update_attributes (photo_params) flash [: success] = 'Photo edited!' redirect_to photos_path en lugar de eso 'editar' final fin

Lo mismo _formar Se utilizará parcial:

vistas / fotos / edit.html.erb

Editar foto

<%= render 'form' %>

Agradable, pero no lo suficiente: los usuarios aún no pueden eliminar una imagen cargada. Para permitir esto, tendremos que adivinar qué otro plugin: 

modelos / image_uploader.rb

plugin: remove_attachment

Utiliza un atributo virtual llamado :quita la imagen, así que permítelo dentro del controlador:

photos_controller.rb

def photo_params params.require (: photo) .permit (: title,: image,: remove_image) end

Ahora solo muestra una casilla de verificación para eliminar una imagen si un registro tiene un archivo adjunto:

vistas / fotos / _form.html.erb

<% if @photo.image_data? %> Eliminar el archivo adjunto: <%= f.check_box :remove_image %> <% end %>

Generando una imagen en miniatura

Actualmente mostramos imágenes originales, que no es el mejor enfoque para las vistas previas: las fotos pueden ser grandes y ocupar demasiado espacio. Por supuesto, usted podría simplemente emplear el CSS anchura y altura atributos, pero eso es una mala idea también. Verá, incluso si la imagen está configurada para ser pequeña usando estilos, el usuario todavía necesitará descargar el archivo original, que podría ser bastante grande..

Por lo tanto, es mucho mejor generar una imagen de vista previa pequeña en el lado del servidor durante la carga inicial. Esto implica dos complementos y dos gemas adicionales. En primer lugar, caer en las gemas:

gema "image_processing" gem "mini_magick", "> = 4.3.5"

Image_processing es una gema especial creada por el autor de Shrine. Presenta algunos métodos de ayuda de alto nivel para manipular imágenes. Esta gema, a su vez, se basa en mini_magick, un envoltorio de Ruby para ImageMagick. Como ha adivinado, necesitará ImageMagick en su sistema para ejecutar esta demostración..

Instala estas nuevas gemas:

instalación de paquete

Ahora incluye los complementos junto con sus dependencias:

modelos / image_uploader.rb

requiere "image_processing / mini_magick" clase ImageUploader < Shrine include ImageProcessing::MiniMagick plugin :processing plugin :versions # other code… end

El procesamiento es el complemento que nos permite manipular una imagen (por ejemplo, reducirla, rotarla, convertirla a otro formato, etc.). Las versiones, a su vez, nos permiten tener una imagen en diferentes variantes. Para esta demostración, se almacenarán dos versiones: "original" y "pulgar" (redimensionadas a 300x300).

Aquí está el código para procesar una imagen y almacenar sus dos versiones:

modelos / image_uploader.rb

clase ImageUploader < Shrine process(:store) do |io, context|  original: io, thumb: resize_to_limit!(io.download, 300, 300)  end end

resize_to_limit! Es un método proporcionado por la gema image_processing. Simplemente reduce una imagen a 300x300 si es más grande y no hace nada si es más pequeño. Además, mantiene la relación de aspecto original..

Ahora, cuando se muestra la imagen, solo debe proporcionar la :original o :pulgar argumento a la URL de la imagen método:

vistas / fotos / _photo.html.erb

<% if photo.image_data? %> <%= image_tag photo.image_url(:thumb) %> <% end %>

<%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %>

Lo mismo se puede hacer dentro del formulario:

vistas / fotos / _form.html.erb

<% if @photo.image_data? %> <%= image_tag @photo.image_url(:thumb) %> Eliminar el archivo adjunto: <%= f.check_box :remove_image %> <% end %>

Para eliminar automáticamente los archivos procesados ​​después de que se complete la carga, puede agregar un complemento llamado delete_raw:

modelos / image_uploader.rb

plugin: delete_raw

Metadatos de la imagen

Además de representar realmente una imagen, también puede obtener sus metadatos. Por ejemplo, veamos el tamaño de la foto original y el tipo MIME:

vistas / fotos / _photo.html.erb

<% if photo.image_data? %> <%= image_tag photo.image_url(:thumb) %>

tamaño <%= photo.image[:original].size %> bytes
Tipo MIME <%= photo.image[:original].mime_type %>

<% end %>

<%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %>

¿Qué pasa con sus dimensiones? Desafortunadamente, no se almacenan de forma predeterminada, pero esto es posible con un complemento llamado store_dimensions.

Dimensiones de la imagen.

El plugin store_dimensions se basa en la gema de fastimage, así que conéctalo ahora:

gema 'fastimage'

No te olvides de correr:

instalación de paquete

Ahora solo incluye el plugin:

modelos / image_uploader.rb

plugin: store_dimensions

Y mostrar las dimensiones utilizando el anchura y altura métodos:

vistas / fotos / _photo.html.erb

<% if photo.image_data? %> <%= image_tag photo.image_url(:thumb) %>

tamaño <%= photo.image[:original].size %> bytes
Tipo MIME <%= photo.image[:original].mime_type %>
Dimensiones <%= "#photo.image[:original].widthx#photo.image[:original].height" %>

<% end %>

<%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %>

Además, hay una dimensiones Método disponible que devuelve una matriz que contiene ancho y alto (por ejemplo,, [500, 750]).

Moviéndose a la nube

Los desarrolladores a menudo eligen los servicios en la nube para alojar los archivos cargados, y Shrine sí presenta tal posibilidad. En esta sección, le mostraré cómo cargar archivos en Amazon S3.

Como primer paso, incluye dos gemas más en el Gemfile:

gem "aws-sdk", "~> 2.1" grupo: desarrollo do gem 'dotenv-rails end

Se requiere aws-sdk para trabajar con el SDK de S3, mientras que dotenv-rails se utilizará para administrar las variables de entorno en desarrollo.

instalación de paquete

Antes de continuar, debe obtener un par de claves para acceder a S3 a través de la API. Para obtenerlo, inicie sesión (o regístrese) en Amazon Web Services Console y navegue hasta Credenciales de seguridad> Usuarios. Cree un usuario con permisos para manipular archivos en S3. Aquí está la política simple que presenta acceso completo a S3:

"Versión": "2016-11-14", "Declaración": ["Efecto": "Permitir", "Acción": "s3: *", "Recurso": "*"]

Descargar el par de claves del usuario creado. Alternativamente, puede usar claves de acceso de root, pero desalentar fuertemente Usted de hacer eso, ya que es muy inseguro.

A continuación, cree un cubo S3 para alojar sus archivos y agregue un archivo a la raíz del proyecto para alojar su configuración:

.env

S3_KEY = YOUR_KEY S3_SECRET = YOUR_SECRET S3_BUCKET = YOUR_BUCKET S3_REGION = YOUR_REGION

Nunca exponer este archivo al público, y asegúrese de excluirlo de Git:

.gitignore

.env

Ahora modifique la configuración global de Shrine e introduzca un nuevo almacenamiento:

config / initializers / shrine.rb

require "shrine" require "shrine / storage / s3" s3_options = access_key_id: ENV ['S3_KEY'], secret_access_key: ENV ['S3_SECRET'], región: ENV ['S3_REGION'], bucket: ENV ['S3_BUCKET'] , Shrine.storages = caché: Shrine :: Storage :: FileSystem.new ("public", prefijo: "uploads / cache"), store: Shrine :: Storage :: S3.new (prefix: "store", ** s3_options),

¡Eso es! No es necesario realizar cambios en las otras partes de la aplicación, y puede probar este nuevo almacenamiento de inmediato. Si recibe errores de S3 relacionados con claves incorrectas, asegúrese de copiar con precisión la clave y el secreto, sin espacios al final ni símbolos especiales invisibles.

Conclusión

Hemos llegado al final de este artículo. Es de esperar que ahora ya tenga mucha confianza en el uso de Shrine y esté ansioso por utilizarlo en uno de sus proyectos. Hemos discutido muchas de las características de esta gema, pero hay aún más, como la capacidad de almacenar contexto adicional junto con los archivos y el mecanismo de carga directa.. 

Por lo tanto, sírvase consultar la documentación de Shrine y su sitio web oficial, que describe detalladamente todos los complementos disponibles. Si tienes otras preguntas sobre esta joya, no dudes en publicarlas. Te agradezco por estar conmigo, y te veré pronto.!