#253 CarrierWave File Uploads
- Download:
- source codeProject Files in Zip (480 KB)
- mp4Full Size H.264 Video (21 MB)
- m4vSmaller H.264 Video (11.7 MB)
- webmFull Size VP8 Video (24.9 MB)
- ogvFull Size Theora Video (25.4 MB)
Supongamos que estamos construyendo una aplicación que permite que usuarios artistas puedan subir y mostrar sus cuadros como galerías. Hay una parte del sitio que ya está construida: se trata de una página que lista las galerías con un enlace a cada una de ellas y una página que muestra las imágenes que hay en cada galería.
Cada cuadro tiene un nombre y el hueco correspondiente a cada imagen. Si tratamos de añadir un nuevo cuadro a la galería veremos que hay un campo de texto para el nombre, pero no hay forma de subir una imagen.
¿Cómo implementar la funcionalidad de adjuntar una imagen a un cuadro? La gema Paperclip, que ya vimos en el episodio 134 [verlo, leerlo], puede controlar campos de ficheros adjuntos en modelos de Rails, pero esta vez vamos a utilizar una nueva gema llamada CarrierWave.
CarrierWave es más flexible que Paperclip. Está basado en Rack, lo que significa que funciona con Rails, Sinatra y cualquier otro framework sobre Rack, y soporta varios ORMs incluyendo ActiveRecord, DataMapper y Mongoid. Otra diferencia importante entre CarrierWave y Paperclip es que CarrierWave lo mantiene todo en una clase separada a donde va a parar toda la lógica de procesamiento de los ficheros adjuntos. De esta forma la gestión de ficheros adjuntos no acaba dispersa por las clases de modelos de la aplicación.
Instalación de CarrierWave
Lo primero es añadir la gema CarrierWave a nuestra aplicación. La gema se instala como es habitual añadiendo una referencia en el fichero Gemfile
de la aplicación.
gem 'carrierwave'
Tenemos que ejecutar la orden bundle
para que la gema quede instalada en nuestro sistema. Una vez esté instalado lo primero que tenemos que hacer es generar la clase que gestionará las subidas. CarrierWave viene con un generador llamado uploader
que hace precisamente esto y al cual se le pasa el nombre que queremos dar a dicha clase, en este caso image
.
$ rails g uploader image create app/uploaders/image_uploader.rb
El generador creará un nuevo directorio llamado uploaders
bajo el directorio app
de la aplicación, y dentro de éste un fichero llamado image_uploader
. En este fichero habrá varios comentarios explicando cómo personalizar la clase. Por ejemplo hay código para cambiar la ubicación de subida, para procesar la imagen después de subirla y para restringir el tipo de los archivos que se pueden subir. Dejaremos los valores por defecto y ya lo personalizaremos todo más tarde.
A continuación tenemos que añadir esta clase al modelo Painting
. Necesitamos una columna en la tabla paintings
, así que generaremos una migración para ello.
$ rails g migration add_image_to_paintings image:string
Nótese que la nueva columna es una cadena que tiene el mismo nombre que le dimos a la clase cuando la generamos. Ejecutaremos rake db:migrate
para actualizar la base de datos.
Lo siguiente será vincular nuestra clase con el modelo Painting
llamando al método mount_uploader
pasándole el nombre de la columna generada anteriormente y la clase. También tenemos que añadir la columna image
a la lista de attr_accessible
para que se pueda acceder a ella desde los controladores.
class Painting < ActiveRecord::Base attr_accessible :gallery_id, :name, :image belongs_to :gallery mount_uploader :image, ImageUploader end
Con todo esto ya podemos modificar el formulario de creación o edición de un cuadro para que tenga un campo de subida de archivo. Tendremos que modificar el formulario para que pueda gestionar adjuntos multiparte añadiendo la opción :html
a form_for
.
<%= form_for @painting, :html => {:multipart => true} do |f| %> <%= f.error_messages %> <%= f.hidden_field :gallery_id %> <p> <%= f.label :name %><br /> <%= f.text_field :name %> </p> <p> <%= f.file_field :image %> </p> <p><%= f.submit %></p> <% end %>
Visualización de las imágenes subidas
Cuando ahora editemos un cuadro veremos que hay un campo de subida de archivos en el formulario a través del cual podemos subir un fichero de imagen. Tras hacerlo aparecerá un mensaje indicando que la subida ha sido correcta, pero la foto aún no saldrá en la galería.
Tenemos que mostrar la imagen que se ha subido. En la plantilla show
de GalleryController
está el código que recorre cada cuadro de la galería y muestra una imagen por defecto en un div
. Vamos a reemplazarlo por la imagen propiamente dicha utilizando image_tag
, que enlazará a la image_url
de cada objeto Painting
. Este método devolverá nil
para los cuadros a los que no se haya subido una imagen, por lo que usaremos to_s
para que esto funcione con image_tag
.
<div id="paintings"> <% @gallery.paintings.each do |painting| %> <div class="painting"> <%= image_tag painting.image_url.to_s %> <div class="name"><%= painting.name %></div> <div class="actions"> <%= link_to "edit", edit_painting_path(painting) %> | <%= link_to "remove", painting, :confirm => 'Are you sure?', :method => :delete %> </div> </div> <% end %> <div class="clear"></div> </div>
Si recargamos la página ya veremos que aparece la imagen que hemos subido pero será más grande que la imagen por defecto.
Podemos reescalar la imagen para que se ajuste. Si miramos en la clase ImageUploader
veremos que hay código comentado para procesar las imágenes:
# Process files as they are uploaded: # process :scale => [200, 300]
No queremos cambiar el tamaño de la imagen original, sino que queremos generar una imagen en miniatura diferente y mostrar un enlace a la imagen en tamaño original. La clase ImageUploader
dispone de un método version
que hará esto por nosotros. El método process
es bastante flexible y recibe un hash de opciones, tras lo cual recorrerá cada clave, buscando un método que se llame igual que la clave al que le pasará el valor de dicha clave como parámetros. Por ejemplo para crear una imagen en miniatura podemos escribir algo como.
# Create different versions of your uploaded files: version :thumb do process :scale => [50, 50] end def scale(width, height) # do something end
No nos interesa escribir nosotros el método de escalado. Afortunadamente podemos hacer que RMagick haga este trabajo por nosotros, incluyendo el módulo CarrierWave::RMagick
para habilitar el funcionamiento con RMagick. Si vemos la documentación del módulo RMagick veremos los métodos que incluye. Hay métodos para reescalar la imagen, incluyendo uno llamado resize_to_limit
que cambiará el tamaño de la imagen pero manteniendo las proporciones. Para usarlo sólo tenemos que descomentar la siguiente línea en nuestra clase:
include CarrierWave::RMagick
Podemos luego descomentar el método version
y llamar a process :resize_to_limit
en el bloque. Este método recibe un alto y un ancho máximos como argumento. Nosotros restringiremos el tamaño de las miniaturas a un máximo de 200x200.
version :thumb do process :resize_to_limit => [200, 200] end
Para que esto funcione tenemos que incluir la gema RMagick en el Gemfile
.
gem "rmagick"
Como siempre, tenemos que ejecutar bundle
para instalar la gema. RMagick depende de ImageMagick, por lo que tenemos que tener instalada la última versión de ésta antes de instalar la gema RMagick. Si usamos OS X podemos instalar ImageMagick mediante HomeBrew o MacPorts.
Para mostrar la miniatura en la vista en lugar de la versión a tamaño completo podemos especificar qué versión queremos mostrar pasándole un argumento a image_url
.
<%= image_tag painting.image_url(:thumb).to_s %>
Podemos probar esto subiendo la imagen de nuevo de forma que se vuelva a procesar su miniatura. Tras hacerlo veremos la miniatura en la galería.
Subida de imágenes mediante una URL
Otra funcionalidad de CarrierWave es la posibilidad de añadir fotografías mediante una URL en lugar de subirlas directamente. Podemos hacerlo añadiendo en el formulario de subida un nuevo campo llamado remote_image_url
. El nombre es importante, porque cuando CarrierWave ve un campo llamado así sabe que tiene que recuperar la imagen a partir de una URL.
<%= form_for @painting, :html => {:multipart => true} do |f| %> <%= f.error_messages %> <%= f.hidden_field :gallery_id %> <p> <%= f.label :name %><br /> <%= f.text_field :name %> </p> <p> <%= f.file_field :image %> </p> <p> <%= f.label :remote_image_url, "or image URL" %> <%= f.text_field :remote_image_url %> </p> <p><%= f.submit %></p> <% end %>
Como hemos añadido un nuevo campo al formulario de painting
tendremos que añadirlo también a la lista de campos en attr_accessible
del modelo Painting
.
class Painting < ActiveRecord::Base attr_accessible :gallery_id, :name, :image, :remote_image_url belongs_to :gallery mount_uploader :image, ImageUploader end
Ahora en el formulario tendremos un campo remote_image_url
en el que podemos introducir una URL para añadir una imagen al cuadro sin tener que subirla manualmente.
Si ahora enviamos el formulario se descargará la imagen remota y se procesará su miniatura.
Con esto concluimos este episodio, pero CarrierWave va mucho más allá: se recomienda visitar su documentación. Una funcionalidad muy útil es la posibilidad de hacer que la subida de imágenes persista a pesar de que se recargue el formulario. Si se añade un campo :image_cache
al formulario y se produce un error de validación en el mismo, el archivo subido permanecerá y no habrá que subirlo otra vez. También se puede añadir una caja de selección para que los usuarios puedan eliminar la imagen, y hacer que los archivos se suban al servicio S3 de Amazon.