General
Shrine es una gema para manejar de manera simple la tarea de subir y adjuntar archivos.
¿Por qué la usamos?
Anteriormente usábamos Paperclip, hasta que fue deprecada en favor de la solución incluida en Rails, ActiveStorage. Dado esto, nos cambiamos a ActiveStorage (AS) para no quedarnos con una herramienta sin soporte.
Sin embargo, hay algunos detalles que se manejan mejor en Shrine, por ejemplo:
Shrine tiene un diseño modular, utilizando
plugins
para las distintas funcionalidades que ofrece. Esto permite cargar solo las que en verdad se usenShrine promueve separación de responsabilidades, introduciendo el concepto de
Uploader
. Estos se encargan de la lógica de subida de un tipo de archivo en particularAS no tiene validaciones, por ejemplo de tamaño o tipo de archivo. En Shrine se pueden agregar fácilmente usando el plugin validation_helpers
Recién en Rails 6.1 se está dando soporte a acceso público de archivos, con Shrine se puede definir a nivel de configuración o por
Uploader
Con Shrine se pueden setear
default_url
por Uploader, para cuando el archivo esnil
¿Cómo la usamos?
Instalación
La gema viene instalada si el proyecto se generó usando Potassium con la opción Shrine
elegida como storage. También se incluyen un par de uploaders que sirven como base. Si el proyecto se generó sin una opción de storage, se puede agregar corriendo potassium install file_storage
y seleccionando Shrine
.
Ejemplo básico
Para estos ejemplos se utilizó la versión 3.2.1 de Shrine
Supongamos que tenemos un Uploader
de imágenes genérico, que podría verse así:
Aquí tenemos el plugin validation_helpers, que nos permite usar validate_mime_type
para verificar que el tipo del archivo corresponda efectivamente a una imagen.
Con esto ya podemos empezar a incluir imágenes en los modelos. Si queremos un attachment
llamado photo
habría que agregar la columna photo_data
a la tabla (tipo text
o jsonb
) y agregar lo siguiente en el modelo:
El nombre de la columna siempre debe incluir el sufijo _data
Ejemplo: heredando de un uploader
Ahora, imaginemos que se quiere agregar una imagen de perfil para los usuarios. Podríamos usar el ImageUploader
definido antes, pero se quieren un par de cosas extra para esta profile_picture
:
Límite de peso, 5 MB. Pueden haber muchas imágenes de perfil distintas en una misma vista y no queremos que quede muy pesada
El usuario puede elegir no tener una foto de perfil
Comúnmente se usará la imagen en dos tamaños
Como esta sigue siendo una imagen y queremos mantener la validación definida en ImageUploader
, vamos a definir un nuevo uploader que herede de este:
Límite de peso
Para esto recurrimos nuevamente al validations_helper
, esta vez usando validate_max_size
. Para mantener las validaciones de la clase padre, se debe llamar a super()
, quedando así:
Sin foto de perfil
En estos casos queremos poner una imagen por defecto, que indique claramente la ausencia de la foto de perfil. Digamos que tenemos una imagen para este propósito en /app/assets/images/no-profile-picture.png
. Podemos usar el plugin default_url
para usarla siempre que no haya una imagen de perfil:
ActionController::Base.helpers.image_url
nos ayuda a obtener la url final de uno de los assets del proyecto.
Con esto, todo usuario cuya profile_picture
sea nil
retornará la url del asset al hacer user.profile_picture_url
.
Distintos tamaños
Vamos a procesar la imagen para tener dos tamaños aparte del original: small
y medium
. Shrine tiene dos opciones para realizar el procesamiento: dinámicamente cuando se pide la transformación (on-the-fly) o con anterioridad, guardando el resultado. Para este ejemplo usaremos la segunda opción.
Como prerequisito necesitamos agregar la gema image_processing. Luego podemos definir las derivatives
para los tamaños small
y medium
:
Después en el controlador se debe gatillar la creación de estas derivadas:
Y para acceder a la url de una de las derivadas:
Resultado
Con todo esto, nuestro Uploader
que hereda de ImageUploader
quedaría así:
Y para agregar el attachment al modelo de usuario:
Recursos útiles
Documentación oficial: muy buena, con guías y secciones explicando los distintos plugins
Direct S3 Upload / Direct App Upload: ambos sirven para subir un archivo antes de que se le haga submit a un form. La diferencia radica en que el de S3 lo sube directamente a AWS, mientras que el otro lo sube a un
upload_endpoint
de la aplicación. Para ambientes que no tienen un bucket S3 (comodevelopment
, en que se guardan los archivos en el filesystem) habría que usar Direct App UploadDemo Direct Upload + Vue: presentación al equipo de Platanus para introducir Shrine. Se arma un componente Vue para manejar el Direct Upload que puede ser usado dentro de un form de Rails
Testing: en la sección de Test data dan un ejemplo de un helper que puede ser usado en las factories. En la sección de Acceptance tests dan un ejemplo de como agregar un archivo como parámetro en tests de controladores usando
Rack::Test::UploadedFile
Recursos útiles para plataneros
Implementación Direct Upload: PR implementando Direct Upload para ser usado en ActiveAdmin
Última actualización