General
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 usen - Shrine 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 particular - AS 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
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
.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í:class ImageUploader < Shrine
plugin :validation_helpers
Attacher.validate do
validate_mime_type %w[image/jpeg image/jpg image/png image/svg+xml image/gif]
end
end
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:include ImageUploader::Attachment(:photo)
El nombre de la columna siempre debe incluir el sufijo _data
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:class ProfilePictureUploader < ImageUploader
end
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í:Attacher.validate do
super()
validate_max_size 5*1024*1024
end
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:plugin :default_url
Attacher.default_url do |**options|
ActionController::Base.helpers.image_url('no-profile-picture.png')
end
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
:require "image_processing/vips"
...
plugin :derivatives
Attacher.derivatives do |original|
vips = ImageProcessing::Vips.source(original)
{
small: vips.resize_to_limit!(300, 300),
medium: vips.resize_to_limit!(500, 500),
}
end
Después en el controlador se debe gatillar la creación de estas derivadas:
user = User.new(..., profile_picture: file)
if user.valid?
user.profile_picture_derivatives! if user.profile_picture_changed?
user.save
end
Y para acceder a la url de una de las derivadas:
User.last.profile_picture_url(:small)
Resultado
Con todo esto, nuestro
Uploader
que hereda de ImageUploader
quedaría así:require "image_processing/vips"
class ProfilePictureUploader < ImageUploader
plugin :default_url
plugin :derivatives
Attacher.validate do
super()
validate_max_size 5*1024*1024
end
Attacher.default_url do |**options|
ActionController::Base.helpers.image_url('no-profile-picture.png')
end
Attacher.derivatives do |original|
vips = ImageProcessing::Vips.source(original)
{
small: vips.resize_to_limit!(300, 300),
medium: vips.resize_to_limit!(500, 500)
}
end
end
Y para agregar el attachment al modelo de usuario:
include ProfilePictureUploader::Attachment(:profile_picture)
- 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 Upload - Demo 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
Última actualización 7mo ago