Blog

Como subir una imagen usando PaperClip a Amazon S3 con Rails 4 Blog

Tags: ruby rails
En este tutorial voy a mostrar como podemos subir imágenes/archivos en Ruby on Rails usando Paperclip para recortar la imagen en diferentes tamaños y luego subirlas a Amazon S3.

Primero es necesario agregar las siguientes gemas al Gemfile:

gem 'rmagick'
gem 'aws-sdk', '~> 1.5.7' #amazon S3 storage for paperclip
gem "paperclip", '~> 4.1'

Puedes seguir el siguiente tutorial para instalar rmagick sin problemas en mac os. Una vez que has agregado esas gemas al Gemfile ejecutas el bundle install.

Lo siguiente es generar la migración para el modelo que necesitamos agregar la imagen, para este ejemplo crearemos un modelo llamado MediaFile que representará el archivo que vamos a subir, de esta manera podemos agregarle tags, relacionarlo con otros modelos, etc.

$ rails g model MediaFile

Agregaremos el campo nombre, referencias polimorficas para asociarlo con cualquier otro modelo y los campos que contendrán la información del archivo que subiremos. La migración nos debe quedar así:

class CreateMediaFiles < ActiveRecord::Migration
  def change
    create_table :media_files do |t|
        t.string  :name
        t.references :reference, polymorphic: true
        t.attachment :file
        t.timestamps
    end
  end
end
Ejecutamos la migración de la siguiente manera.

$ rake db:migrate

Configurando credenciales de S3

Es necesario configurar las credenciales de Amazon S3 en nuestra aplicación para poder subir los archivos a nuestra cuenta, existen varias maneras de hacer esto, pero la que me ha parecido mejor, es simplemente definir la configuraciónde manera global en el archivo config/application.rb con el siguiente código.

config.paperclip_defaults = {
    :storage => :s3,
    :s3_credentials => {
        :access_key_id => ENV['S3_KEY'],
        :secret_access_key => ENV['S3_SECRET'],
        :bucket => ENV['S3_BUCKET']
    }
}

Aqui hemos definido nuestras credenciales como variables de entorno, de esta manera podremos cambiar el valor de estas variables directamente en nuestro sistema, esto nos ayudará a poder deployar en Heroku de una manera bastante sencilla, además de que mantenemos fuera de nuestro código esta información.

Dependiendo tu sistema es como definirás las variables de entorno, pero es muy importante definirlas de lo contrarío no tendrás acceso a tu cuenta en amazon.

Definición del modelo MediaFile

Ya ejecutamos la migración en el paso anterior, ahora agregamos lo siguiente a la definición del modelo MediaFile.

class MediaFile < ActiveRecord::Base

    has_attached_file   :file,
                        :default_url => '/assets/no-image.png',
                        :styles => { :small => ["180x100!",:jpg], :meddium => ["600x400!",:jpg]},
                        :default_style => :meddium,
                        :storage => :s3,
                        :path => "uploads/:file_id/:style/:filename"
end

El nombre que usaremos para el archivo (file) tiene que llamarse igual al que usamos en la migración anterior. 

  1. Mediante la propiedad :default_url definimos la imagen que mostraremos si el usuario aún no ha subido algún avatar.
  2. En la propiedad :styles definimos los tamaños en que redimencionaremos la imagen al ser guardada, aquí podemos definir todos los estilos que necesitemos siguiendo el mismo formato.
  3. La propiedad :default_style la usamos para definir el estilo por default cuando llamemos al método url sin ningún parámetro. Esta propiedad es útil para cuando definimos más de un estilo.
  4. En la propiedad :storage estamos indicando que vamos a utilizar Amazon S3 para guardar nuestras imágenes.
  5. La propiedad :path es importante definirla si queremos tener ordenadas las imágenes en la cubeta del S3. Esta configuración es muy recomendable hacerla, enseguida explicaré un poco más al respecto.

Definiendo el Path

Es importante definir el path en el que guardaremos las imágenes en el S3, de no hacerlo podríamos tener conflictos en el futuro además de que sería un total desorden.

Si podemos observar, el path asignado es uploads/:file_id/:style/:filename, aquí estamos utilizando algunos símbolos que serán remplazados dinámicamente. 

El símbolo :style y :filename están definidos por paperclip, donde :style representa los estilos que definimos, en este caso solo definimos tns y medium por defecto también se crea otro llamado original. El símbolo :filename será remplazado por el nombre de la imagen que el usuario estará subiendo.

El símbolo :file_id es nuestro, la idea es que aquí se remplace por el ID del registro que se crea (en la base de datos) para la imagen que se esta subiendo, para eso es necesario definir este símbolo en el archivo config/initializers/paperclip.rb de la siguiente manera.

Paperclip.interpolates :file_id do |attachment, style|
  attachment.instance.id.to_s
end

Al crear el símbolo :file_id vemos que recibe como parámetros dos variables, lo que a nosotros nos interesa es tomar el ID de la instancia y pasarlo a string para ser concatenado en el path.

Aquí podríamos hacer lo que necesitemos, posiblemente generar un hash random para crear URLs más complejas y difíciles de adivinar, pero en nuestro caso simplemente regresamos el id de la instancia.

Creando el formulario

Vamos a crear un formulario muy sencillo para este ejemplo donde solamente mostraremos un textfield con el nombre que le daremos y un botón para subir la imagen.

Primeramente debemos crear el método new dentro de nuestro controlador.

def new
    @media = MediaFile.new
end

Lo siguiente es crear la vista new.html.erb de la siguiente manera.

<%= form_for(@media,url: admin_manager_path,html: {method: 'post'}) do |f| %>
  
    <%= f.label :name,'Caption' %>
    <%= f.text_field :name %>
  
      <%= f.label :file,'Media File' %>
      <%= f.file_field :file %>
  
    <button type="submit" name="commit" class="btn">Upload File</button>
<% end %>

En el código anterior se crea el formulario pasando como primer parámetro la instancia vacía del modelo MediaFile, para la url he utilizado un helper que me regresa una url definida en mi archivo router.rb que apunta al método create de mi controlador.

Lo siguiente es definir los campos del formulario, que en este caso solo es un texfield y un file, siempre puedes definir todos los que necesites.

Haciendo el Upload

Una vez que tenemos nuestra vista vamos a subir el archivo, para eso necesitamos definir en nuestro controlador el método create donde procesaremos el archivo y la información que estaremos subiendo.

def create
    @media = MediaFile.new media_params

    if @media.save
        flash[:success] = "File successfully saved."
        redirect_to edit_admin_artwork_path @media
    else
        flash[:error] = @media.errors.full_messages.join('<br />')
        render 'new'
    end
end

private

    def media_params
      params.require(:media_file).permit(:name,:file)
    end

Nada complicado, simplemente recogiendo los parámetros y asegurándonos que sean los correctos, luego guardamos el model y automáticamente la imagen se enviará a nuestra cuenta en Amazon S3.

Por último podemos crear la vista de edición para poder cambiar la imagen o el nombre. Si has seguido el tutorial ya podrás ver en tu cuenta de amazon s3 la imagen que subimos.

Happy coding!

1Comentario

Instructor del curso

Crysfel3

Autor: Crysfel Villa

Soy un geek que disfruta crear y compartir cosas en internet! Sígueme en twitter @crysfel