1. Code
  2. Ruby
  3. Ruby on Rails

Carga de Imagen en Rails: Usando Paperclip en una Aplicación Rails

Scroll to top

Spanish (Español) translation by Rafael Chavarría (you can also view the original English article)

En las primeras dos partes de esta serie, te mostré como habilitar la carga de imágenes en Rails usando CarrierWave. En esta parte, te mostraré cómo hacerlo usando Paperclip.

Paperclip es un gem de Ruby proporcionado por thoughtbot. Fue creado para hacer el adjuntar archivos muy fácil. En este tutorial, verás cómo usar Paperclip junto con Devise.

Sin mucha charla, pongamos manos a la obra.

Paperclip requiere la instalación de ImageMagick en tu máquina. Necesitas esto para procesamiento de imagen. Para instalar ImageMagick, usar cualquier de los pasos de abajo, dependiendo del tipo de máquina que uses.

Usuarios Mac:

brew install imagemagick

Usuarios Ubuntu:

sudo apt-get install imagemagick

Generación de Aplicación Rails

Usa tu terminal para generar una nueva aplicación.

rails new paperclip

Abre tu Gemfile y agrega los gems necesarios:

gem 'paperclip'

gem 'devise'

Ejecuta bundle install cuando termines.

Configuración Devise

Desde tu termina, instala devise usando el comando de abajo:

rails generate devise:install

Cuando eso esté terminado, ahora puedes generar tu modelo Usuario:

rails generate devise User

Migra tu base de datos después.

rake db:migrate

Genera tus vistas devise.

rails generate devise:views

Usando tu editor de texto, navega a app/views/layouts/application.html.erb y agrega el siguiente código justo arriba de bloque yield.

1
#app/views/layouts/application.html.erb
2
3
<p class="notice"><%= notice %></p>
4
<p class="alert"><%= alert %></p>

Integración Paperclip

Debido a razones de seguridad, tenemos que permitir parámetros en el controlador Devise. Gracias al increíble equipo detrás de Devise, hacer esto es sencillo.

Abre app/controllers/application_controller.rb y pega las siguientes líneas de código.

1
#app/controllers/application_controller.rb
2
3
class ApplicationController < ActionController::Base
4
  # Prevent CSRF attacks by raising an exception.
5
  # For APIs, you may want to use :null_session instead.
6
7
  protect_from_forgery with: :exception
8
  
9
  before_action :configure_permitted_parameters, if: :devise_controller?
10
11
  protected
12
13
  def configure_permitted_parameters
14
    devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :password, :password_confirmation, :remember_me, :avatar, :avatar_cache) }
15
    devise_parameter_sanitizer.for(:account_update) { |u| u.permit(:username, :password, :password_confirmation, :current_password, :avatar) }
16
  end
17
end
18

Abre tu modelo User y hazlo lucir como esto:

1
#app/models/user.rb
2
3
class User < ActiveRecord::Base
4
  # Include default devise modules. Others available are:
5
  # :confirmable, :lockable, :timeoutable and :omniauthable
6
  devise :database_authenticatable, :registerable,
7
         :recoverable, :rememberable, :trackable, :validatable
8
9
  has_attached_file :avatar, styles: { medium: "300x300", thumb: "100x100" }
10
  validates_attachment_content_type :avatar, content_type: /\Aimage\/.*\Z/
11
end

Necesitas agregar una columna avatar para tu tabla Users. Ahí hay un comando rails que hace esto posible desde tu terminal.

rails generate migration add_avatar_to_users

Eso creará una nueva migración en db/migrate. Ábrela y pega el siguiente código:

1
class AddAvatarToUsers < ActiveRecord::Migration
2
  def up
3
    add_attachment :users, :avatar
4
  end
5
6
  def down
7
    remove_attachment :users, :avatar
8
  end
9
end

Ejecuta tu migración

rake db:migrate

Agrega Avatar a Formularios Devise

Editarás tu nuevo formulario de registro app/views/devise/registrations/new.html.erb y edita el formulario app/views/devise/registrations/edit.html.erb a lo que tengo abajo:

1
#app/views/devise/registrations/new.html.erb
2
3
<h2>Sign up</h2>
4
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { multipart: true }) do |f| %>
5
  <%= devise_error_messages! %>
6
  <div class="field">
7
    <%= f.label :email %><br />
8
    <%= f.email_field :email, autofocus: true %>
9
  </div>
10
11
  <div class="field">
12
    <%= f.label :password %>
13
    <% if @minimum_password_length %>
14
    <em>(<%= @minimum_password_length %> characters minimum)</em>
15
    <% end %><br />
16
    <%= f.password_field :password, autocomplete: "off" %>
17
  </div>
18
19
  <div class="field">
20
    <%= f.label :password_confirmation %><br />
21
    <%= f.password_field :password_confirmation, autocomplete: "off" %>
22
  </div>
23
24
  <div class="field">
25
    <%= f.file_field :avatar %>
26
  </div>
27
28
  <div class="actions">
29
    <%= f.submit "Sign up" %>
30
  </div>
31
<% end %>
32
33
<%= render "devise/shared/links" %>
1
#app/views/devise/registrations/edit.html.erb
2
3
<h2>Edit <%= resource_name.to_s.humanize %></h2>
4
5
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
6
  <%= devise_error_messages! %>
7
8
  <div class="field">
9
    <%= f.label :email %><br />
10
    <%= f.email_field :email, autofocus: true %>
11
  </div>
12
13
  <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
14
    <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
15
  <% end %>
16
17
  <div class="field">
18
    <%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
19
    <%= f.password_field :password, autocomplete: "off" %>
20
  </div>
21
22
  <div class="field">
23
    <%= f.label :password_confirmation %><br />
24
    <%= f.password_field :password_confirmation, autocomplete: "off" %>
25
  </div>
26
27
  <div class="field">
28
    <%= f.file_field :avatar %>
29
  </div>
30
31
  <div class="field">
32
    <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
33
    <%= f.password_field :current_password, autocomplete: "off" %>
34
  </div>
35
36
  <div class="actions">
37
    <%= f.submit "Update" %>
38
  </div>
39
<% end %>
40
41
<h3>Cancel my account</h3>
42
43
<p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>
44
45
<%= link_to "Back", :back %>

Inicia tu navegador y revisa lo que que tienes.

Para una aplicación estándar, podrías querer revisar si un usuario que quiere editar su perfil ya tiene un avatar cargado. Esto es fácil de implementar en tu archivo de edición de registro.

Abre el archivo de registro y hazlo lucir como esto:

1
#app/views/devise/registrations/edit.html.erb
2
3
<h2>Edit <%= resource_name.to_s.humanize %></h2>
4
5
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
6
  <%= devise_error_messages! %>
7
8
  <div class="field">
9
    <%= f.label :email %><br />
10
    <%= f.email_field :email, autofocus: true %>
11
  </div>
12
13
  <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
14
    <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
15
  <% end %>
16
17
  <div class="field">
18
    <%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
19
    <%= f.password_field :password, autocomplete: "off" %>
20
  </div>
21
22
  <div class="field">
23
    <%= f.label :password_confirmation %><br />
24
    <%= f.password_field :password_confirmation, autocomplete: "off" %>
25
  </div>
26
27
  <div class="field">
28
    <%= f.file_field :avatar %>
29
30
    <% if @user.avatar? %>
31
      <%= image_tag @user.avatar.url(:thumb) %>
32
    <% end %>
33
  </div>
34
35
  <div class="field">
36
    <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
37
    <%= f.password_field :current_password, autocomplete: "off" %>
38
  </div>
39
40
  <div class="actions">
41
    <%= f.submit "Update" %>
42
  </div>
43
<% end %>
44
45
<h3>Cancel my account</h3>
46
47
<p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p>
48
49
<%= link_to "Back", :back %>

¿Puedes ver que cambió?

En el código de arriba, hay una declaración condicional para revisar si un avatar ya existe para un usuario usando la línea <% if @user.avatar? %>. Si esto devuelve true, la siguiente línea se ejecuta, de otro modo no.

Validaciones de Seguridad

La validación siempre es importante cuando se habilitan características de carga en tu aplicación web. Paperclip viene con medidas para asegurar tu aplicación.

Puedes usar cualquiera de las validaciones debajo en tu modelo.

1
class User < ActiveRecord::Base
2
  has_attached_file :avatar
3
  # Validate content type
4
  validates_attachment_content_type :avatar, content_type: /\Aimage/
5
  # Validate filename
6
  validates_attachment_file_name :avatar, matches: [/png\Z/, /jpe?g\Z/]
7
  # Explicitly do not validate
8
  do_not_validate_attachment_file_type :avatar
9
end

Conclusión

Podrías querer considerar Paperclip mientras construyes tu siguiente aplicación web. Tiene un gran equipo soportándolo.

Para explorar otras características no cubiertas en este tutorial, revisa la página GitHub de Paperclip.