Cómo Adjuntar Archivos a un Modelo en Rails
Paperclip es un plugin que permite adjuntar archivos de todo tipo a un modelo de ActiveRecord. Fue pensado para que su implementación sea lo más fácil posible y para que los archivos puedan ser tratados simplemente como un atributo más del modelo. Esto además significa que los cambios en el archivo son efectuados sólo una vez que ActiveRecord::Base#save es ejecutado, y que de la misma forma que con los demás atributos de ActiveRecord, también podemos realizar todo tipo de validaciones sobre el archivo.
En este tutorial veremos cómo permitirle a un usuario subir una foto para su perfil. Paperclip se encargará no sólo de guardar la foto en nuestro servidor, sino también de crear automáticamente un thumbnail (vista en miniatura) de esa imagen.
Para el procesar imágenes, Paperclip utilizar una librería muy popular conocida como ImageMagick. Es esta librería la que nos permitirá generar los thumbnails para nuestros usuarios. Comencemos con los requisitos necesarios:
1- Instalación de ImageMagick
Este paso es necesario solamente para aquellos usuarios que no tengan ImageMagick instalado en su equipo, que por lo general son los usuarios de Windows. Las distribuciones de Linux y Mac tienen el paquete instalado por defecto, pero para evitar problemas en el futuro no estaría de más cerciorar que el paquete está presente en el sistema.
Windows
La instalación en Windows es muy simple: hay que descargar uno de los instaladores (ante la duda, el Q16-windows-dll funciona en la mayoría de los sistemas) y ejecutarlo. Una vez dentro del instalador, activar la opción “Update executable search path” para que la librería pueda ser accedida desde todo el sistema.
Ubuntu
Para instalar ImageMagick en Ubuntu o similares (si aún no está instalado), correr el siguiente comando en la consola: sudo apt-get install imagemagick. Si alguien necesita información de cómo hacerlo en otra distribución, dejen un comentario y voy a tratar de responder lo más pronto posible.
2- Instalación de Paperclip
Instalamos Paperclip de la misma forma que con cualquier otro plugin:
script/plugin install git://github.com/thoughtbot/paperclip.git
Para aquellos que prefieran instalarlo como un gem, agregamos en nuestro environment.rb:
config.gem 'paperclip', :source => 'http://gemcutter.org'
Y luego corremos en la consola rake gems:install, para instalar Paperclip en el entorno.
3- Preparar la Base de Datos
Supongamos que ya tenemos un modelo User en donde reside la información sobre un usuario de nuestro sitio. Lo que queremos hacer es brindarle al usuario la posibilidad de subir una imagen de perfil (foto, avatar, etc) a nuestro sitio. Esta imagen va a ser accedida por medio de un atributo de User que llamaremos picture. Por suerte, Paperclip nos ayuda con un generador, que facilitará mucho nuestra tarea.
En la consola:
script/generate paperclip user picture
El generador toma dos parámetros: el primero indica el modelo al que le queremos adjuntar una imagen, en nuestro caso user. El segundo indica el nombre que le daremos (dentro del modelo) a esta imagen adjunta, y aquí utilizamos picture. Lo que hace este generador es crear un nuevo archivo de migración para agregar cuatro campos nuevos a nuestro modelo User:
#db/migrate/20091129014122_add_attachments_picture_to_user.rb
class AddAttachmentsPictureToUser < ActiveRecord::Migration
def self.up
add_column :users, :picture_file_name, :string
add_column :users, :picture_content_type, :string
add_column :users, :picture_file_size, :integer
add_column :users, :picture_updated_at, :datetime
end
def self.down
remove_column :users, :picture_file_name
remove_column :users, :picture_content_type
remove_column :users, :picture_file_size
remove_column :users, :picture_updated_at
end
end
A esta altura debemos correr rake db:migrate para que los cambios se apliquen a la base de datos.
4- Adjuntar Paperclip al Modelo
Una vez hechos los cambios en la base de datos, necesitamos indicarle a nuestro modelo User que queremos un archivo adjunto con nombre picture. Paperclip detectará y utilizará automáticamente los campos que acabamos de agregar a la base de datos. Para hacerlo, agregamos lo siguiente en nuestro modelo:
#app/models/user.rb
class User < ActiveRecord::Base
# Paperclip
has_attached_file :picture,
:styles => {
:thumb=> "100x100#",
:small => "150x150>" }
end
Es importante mencionar que Paperclip aún no sabe que sólo queremos permitir al usuario subir imágenes. Pero más allá de eso, hay un par de parámetros que nos interesan.
El primero es :picture, que le indica a Paperclip el nombre del archivo adjunto. Es importante que coincida con el nombre que decidimos darle en la base de datos en el paso anterior.
El segundo parámetro es :styles. Es utilizado sólamente cuando estamos tratando con imágenes, porque no tiene efecto sobre ningún otro tipo de archivos. Este parámetro toma un Hash de claves y sus correspondientes valores. Cada clave define una nueva imagen que se va a generar a partir de la imagen original, y su valor indica qué formato va a tener. Para ver qué formas puede tomar el valor, referirse a la documentación de ImageMagick. En este caso en particular, se guardarán tres imágenes: la original, una thumb (de tamaño 100×100 y recortada en los bordes) y otra small (de tamaño máximo 150×150). Para ver cómo modificar el nombre y la ruta donde las imágenes son almacenadas del lado servidor, referirse a la documentación de Paperclip Interpolations.
Si queremos evitar que se guarde la imagen original, podemos definir un estilo que se llame :original y Paperclip guardará la imagen con este nuevo formato, descartando la imagen original. Esto es especialmente útil si queremos evitar guardar imágenes muy pesadas en nuestro servidor.
Por último, los estilos también pueden ser Procs (una de las características interesantes de Ruby). Por ejemplo, si le queremos permitir al usuario definir un ancho y un alto para su imagen (y tenemos dichos valores guardados en los atributos picture_width y picture_height de nuestro modelo, respectivamente):
has_attached_file :picture,
:styles => {
:thumb=> "100x100#",
:small => "150x150>" }
:custom => Proc.new {
|user| # devuelve la instancia actual del usuario
"#{user.photo_width}x#{user.photo_height}>" }
}
Sin embargo, en nuestro ejemplo no necesitamos hacer esto, así que nos quedaremos con los estilos thumb y small.
5- Definir Validaciones
Las validaciones de Paperclip son una de sus características más útiles. El plugin nos provee métodos especiales para algunas de las validaciones más comunes, aunque también podemos agregar manualmente las que nos parezca conveniente a través del método validate de ActiveRecord.
Todos las validaciones toman dos parámetros: (nombre, opciones = {}). El primero especifica a qué archivo adjunto queremos aplicar la validación. Esto es sobre todo útil si el modelo que utilizamos debe tener más de un archivo adjunto. El segundo parámetro es un Hash en donde debemos, en la mayoría de los casos, especificar ciertos valores que le darán sentido a la validación. El Hash de opciones admite algunas opciones que son comunes tanto para este tipo de validación de Paperclip como para el resto de las validaciones de ActiveRecord:
:if => Acepta una condición para la validación (importante recordar: de la misma forma que lo hacen las validaciones de ActiveRecord). Ejecuta la validación si la condición evalúa a verdadero.
:unless => Lo mismo que lo anterior, sólo que la validación no se ejecuta si la condición evalúa a verdadero.
:message => Define el mensaje que se mostrará si el archivo adjunto no pasa la validación. Es útil si necesitamos traducir los mensajes de error al Español (u otro idioma), pero si estamos trabajando en inglés los mensajes suelen ser bastante descriptivos.
Los métodos que nos provee Paperclip para validar los archivos adjuntos son:
validates_attachment_presence (nombre, opciones = {})
Funciona igual que el validates_presence_of de ActiveRecord: el modelo no se podrá guardar siempre que no haya un archivo adjunto (al campo especificado por nombre).
validates_attachment_size (nombre, opciones = {})
Valida el tamaño del archivo adjunto. Debemos indicar cómo queremos validarlo a través de las opciones. Algunas opciones de ejemplo son (hay que utilizar sólo una de ellas):
:less_than => 5.megabytes # menos de 5 Mb :greater_than => 500.kilobytes # más de 5 Mb :in => (100..5.megabytes) # de 100 bytes hasta 5 Mb
validates_attachment_content_type (nombre, opciones = {})
Valida que el archivo adjunto pertenezca a los tipos MIME que especificaremos en las opciones a través de la clave :content_type. Un ejemplo:
:content_type => ['image/jpeg', 'image/png']
Con esto en mente, podemos agregarle las validaciones que necesitamos a nuestro modelo User. No utilizaremos validates_attachment_presence porque queremos que el usuario pueda decidir tener una imagen de perfil o no.
#app/models/user.rb
class User < ActiveRecord::Base
# Paperclip
has_attached_file :picture,
:styles => {
:thumb=> "100x100#",
:small => "150x150>" }
# Validaciones de Paperclip
validates_attachment_size :picture, :less_than => 2.megabytes
validates_attachment_content_type :picture, :content_type => ['image/jpeg', 'image/png']
end
6- Las Vistas
Ya casi terminando, veremos que tanto en las Vistas como en los Controladores podemos tratar al modelo como estamos acostumbrados. Un formulario de ejemplo que permite a un usuario subir su foto de perfil:
<!-- app/views/user/new.html.erb -->
<% form_for @user, :html => { :multipart => true } do |form| %>
<ol>
<!-- Otros campos del usuario ... -->
<li>
<%= form.label :picture, "Picture" %>
<%= form.file_field :picture %>
<li>
<%= form.submit "Save Picture!" %>
</li>
</ol>
<% end %>
Como se puede observar, tratamos a nuestro campo picture de Paperclip como si fuera un file_field. Cuando el usuario seleccione una imagen y envíe el formulario al servidor, Paperclip sabrá qué hacer con la imagen y hará todo el trabajo por nosotros. Lo único que tenemos que hacer que puede ser diferente a lo que hayamos hecho hasta ahora, es decirle a Rails que queremos un formulario multipart. Esto es necesario siempre que queremos subir imágenes o archivos (además de datos comunes) en un formulario HTML.
Para mostrar la imagen en cualquier vista podemos utilizar el método url de nuestro objeto Paperclip. Este método devuelve una ruta a la imagen original (sin parámetros) o a las secundarias (con el parámetro apropiado):
<%= image_tag @user.picture.url %> <%= image_tag @user.picture.url(:thumb) %> <%= image_tag @user.picture.url(:small) %>
7- El Controlador
El controlador es lo más fácil, porque si lo hicimos correctamente, no tenemos que cambiar absolutamente nada de código para que Paperclip funcione:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
#...
def new
@user = User.new
end
def create
@user = User.create(params[:user])
end
def update
@user = current_user
if @user.update_attributes(params[:user])
flash[:info] = "Sus datos fueron actualizados con éxito"
redirect_to user_path(@user)
else
render :action => :edit
end
end
def edit
@user = current_user
end
private
def current_user
User.find_by_id(session['user_id'])
end
end
Es importante destacar nuevamente que este código es genérico y puede ser utilizado con o sin Paperclip. Tanto en la acción create como en update, nuestros archivos Paperclip son validados y guardados dentro de ActiveRecord::Base#save!. Esto ocurre tanto dentro del método create como del método update_attributes.
Conclusión
Vimos un ejemplo sencillo de cómo utilizar Paperclip para adjuntar imágenes a nuestros modelos. Vale la pena repetir que Paperclip puede manejar no sólo imágenes, sino cualquier tipo de archivos. Para usos más avanzados de este plugin, referirse a la documentación.
En el futuro seguramente estaré escribiendo más artículos sobre Paperclip. Si le gustó este artículo, puede subscribirse a mi RSS Feed para ser notificado en cuanto salga uno nuevo. También hay un formulario al costado de esta página en donde puede subscribir su email para que las notificaciones le lleguen a su casilla de correo.
Si hay algún error en este artículo, sugerencias, o simplemente ha sido útil, no dude de comentar!
Recursos
Parte de este artículo está basado en los siguientes recursos disponibles en Inglés:
- Repositorio Git de Paperclip
- Paperclip: Attaching Files in Rails (de principios del 2008)
- Goodbye Attachment Fu, hello Paperclip
Un artículo cojonudo y muy didáctico!! Ya me he suscrito, y ahora voy a leerme los demás artículos del blog. Y si algún día vienes por Valencia, no dejes de escribirme…
s2
Muchas gracias Fernando! Me alegra mucho que te haya gustado, y espero que así sea también en el futuro.
Saludos desde Argentina!
Saludos justo tengo un artículo que complementa tu tutor con respecto al testeo de subida de archivos con paperclip que es una gema maravillosa.
http://www.boliviaonrails.com/2009/11/03/testeando-subida-de-archivos-file-uploads-con-cucumber-rspec-y-paperclip/
Hola gaby muy buenos articulos te felicito.. sos un grande me voy a pasar seguido segui asi.. saludos
Luis
Wow Luis, que sorpresa! =D que bueno que te hayan gustado, me alegra mucho. A ver cuándo escribo de nuevo, porque estoy a full con exámenes jaja. Saludos!
Muy bueno el post y el blog en general :D
Ya me he apuntado a tu RSS, espero ver artículos como estos en futuras ocasiones :) Un abrazo!
Muchas gracias! Espero verte de nuevo entonces, y felicitaciones por tu blog también, del que he sacado un par de ideas útiles – sobre todo lo del gmate, que seguro le voy a dar una probada ahora mismo!
Estupendo el post, pero tengo un problemita, el archivo no sube al servidor y la base de datos me la muestra en null, pero solo los campos del picture(en mi caso uso archivos PDF, y lo llamo pdf) no entiendo q puede llegar a pasar… alguna sugerencia? gracias.
Hola Matias! Espero que hayas podido solucionar tu problema, pero en caso contrario te queria preguntar si habias seguido bien las instrucciones de este post y si te fijaste que dicen los logs. Necesitaria un poco mas de detalles para poder ayudarte. Saludos.
Hola. Me vengo rompiendo la cabeza con un problema.
Estoy usando Rails 3.0.9 con ruby 1.8.7 y la version 2.3 de parperclip y todo funciona bien, guarda los datos y todo pero no me muestra la imagen, cuando veo el elemento me muestra una imagen con la leyendo “Missing” y accedo a la ruta de la imagen (http://localhost:3000/images/original/missing.png) me indica el error “No route matches “/images/original/missing.png” ” alguien sabe a qué se debe. Gracias a todos
Hola Josue,
Deberías fijarte bajo qué ruta Paperclip está guardando las imágenes. Primero buscalas manualmente dentro de las carpetas de tu disco, seguramente bajo la carpeta ‘public’. Una vez que las hayas encontrado, fijate en la ruta y asegurate que coincida con la URL. Si no las enctuentras en el disco entonces no las está subiendo directamente. Para más información sobre la ruta a la cual Paperclip sube las imágenes, consulta el siguiente artículo: http://thewebfellas.com/blog/2008/11/2/goodbye-attachment_fu-hello-paperclip y fijate bajo la sección “Rename Files on Upload” cómo hacer para configurar el sitio donde se guardan las imágenes.
Suerte, y si nada funciona no dudes contactarme nuevamente con más detalles!