Inicio > Plugins > Cómo Adjuntar Archivos a un Modelo en Rails

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:

 

  1. 30/11/2009 a las 20:51 | #1

    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

    • 30/11/2009 a las 21:16 | #2

      Muchas gracias Fernando! Me alegra mucho que te haya gustado, y espero que así sea también en el futuro.
      Saludos desde Argentina!

  2. 08/12/2009 a las 10:06 | #3

    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/

  3. Luis
    10/12/2009 a las 14:17 | #4

    Hola gaby muy buenos articulos te felicito.. sos un grande me voy a pasar seguido segui asi.. saludos
    Luis

    • 10/12/2009 a las 14:36 | #5

      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!

  4. 29/12/2009 a las 22:54 | #6

    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!

    • 30/12/2009 a las 20:49 | #7

      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!

  5. Matias
    09/02/2011 a las 18:06 | #8

    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.

    • 25/02/2011 a las 17:40 | #9

      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.

  6. 25/06/2011 a las 17:13 | #10

    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

    • 27/06/2011 a las 04:27 | #11

      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!

  1. Aún no hay trackbacks

Deja un comentario

Fill in your details below or click an icon to log in:

Logo de WordPress.com

You are commenting using your WordPress.com account. Log Out / Cambiar )

Twitter picture

You are commenting using your Twitter account. Log Out / Cambiar )

Facebook photo

You are commenting using your Facebook account. Log Out / Cambiar )

Connecting to %s

Seguir

Get every new post delivered to your Inbox.