homeASCIIcasts

210: Personalización de Devise 

(view original Railscast)

Other translations: En It Cn Ja

Written by Juan Lupión

En el episodio anterior [verlo, leerlo] mostrábamos como configurar devise para la autenticación de usuarios en una aplicación Rails. En esta entrega seguiremos por donde lo dejamos y veremos cómo personalizar Devise.

Continuaremos trabajando con la aplicación del episodio anterior, por lo que ya contamos con páginas para registrarnos e iniciar y cerrar sesión.

Nuestra aplicación de gestión de proyectos.

Restricción de acceso

El siguiente paso que vamos a dar es restringir ciertas acciones para que sólo puedan ser accedidas por usuarios que hayan iniciado sesión. Por ejemplo, sólo estos usuarios deberían poder crear, editar o destruir proyectos. Para esto tendremos que modificar ProjectsController añadiendo un before_filter que invoque un método de Devise llamado authenticate_user!. Este metodo garantiza que el usuario tiene sesión iniciada y si no es así lo redirige a la página de entrada. Las acciones index y show sí que deberían poder ser accesibles para todos los usuarios así que usaremos el parámetro :except para que el filtro no afecte a estas acciones.

/app/controllers/projects_controller.rb

 class
ProjectsController < ApplicationController

 before_filter :authenticate_user!, :except => [:show, :index]

 def index #rest of class 

Si hacemos clic en el enlace “New Project” sin haber iniciado sesión seremos llevados a la página de entrada.

Redirección al inicio de sesión si intentamos crear un proyecto.

Esta técnica funciona bien para casos de autorización simple (sólo tenemos que asegurarnos de que el usuario ha iniciado sesión). Si necesitamos montar un esquema de autorización más sofisticado tendremos que recurrir a una solución como CanCan, que vimos en el episodio 192 [verlo, leerlo] y que puede combinarse con Devise.

Personalización de las vistas en Devise

A continuación veremos como modificar el aspecto de las vistas en Devise, dado que querremos que las vistas proporcionadas por Devise se adapten al aspecto general de nuestras páginas. Al ser un Engine de Rails, Devise proporciona sus propias vistas que pueden adaptarse copiándolas a nuestra propia aplicación. De hecho Devise proporciona un generador para que sea fácil hacer esto. Desde el directorio de nuestra aplicación sólo tenemos que ejecutar rails generate devise_views y se crearán los ficheros de las vistas.

$ rails generate devise_views 
      create  app/views/devise
      create  app/views/devise/confirmations/new.html.erb 
      create  app/views/devise/mailer/confirmation_instructions.html.erb
      create  app/views/devise/mailer/reset_password_instructions.html.erb 
      create  app/views/devise/mailer/unlock_instructions.html.erb 
      create  app/views/devise/passwords/edit.html.erb 
      create  app/views/devise/passwords/new.html.erb 
      create  app/views/devise/registrations/edit.html.erb 
      create  app/views/devise/registrations/new.html.erb 
      create  app/views/devise/sessions/new.html.erb 
      create  app/views/devise/shared/_links.erb 
      create  app/views/devise/unlocks/new.html.erb 

Este comando copia todas las vistas a partir del directorio del Engine por lo que ahora podremos editarlas según nuestras necesidades. A continuación se muestra el código de la vista para la página de registro que vimos anteriormente.

/app/views/devise/sessions/new.html.erb

<h2>Sign in</h2>

<%= form_for(resource_name, resource, :url => session_path(resource_name)) do |f| %> 
  <p><%=f.label :email %></p> 
  <p><%= f.text_field :email %></p>

  <p><%= f.label :password %></p> 
  <p><%= f.password_field :password %></p>

  <% if devise_mapping.rememberable? -%> 
    <p><%= f.check_box :remember_me %> <%= f.label :remember_me %></p> 
  <% end -%>

  <p><%= f.submit "Sign in" %></p>
<% end %>

<%= render :partial => "devise/shared/links" %> 

Y lo cambiaremos por:

/app/views/devise/sessions/new.html.erb

<% title "Sign In" %>

<%= form_for(resource_name, resource, :url => session_path(resource_name)) do |f| %> 
  <ol class="formList"> 
    <li><%= f.label :email %> <%= f.text_field :email %></li> 
    <li><%= f.label :password %> <%= f.password_field :password %></li>
    <% if devise_mapping.rememberable? -%> 
    <li><%= f.check_box :remember_me %> <%= f.label :remember_me %></li> 
    <% end %> <li><%= f.submit "Sign in" %></li>
  </ol> 
<% end %> 

<%= render :partial => "devise/shared/links" %> 

En el código anterior hemos cambiado la cabecera incluyendo una llamada al método title que nos facilita el plugin nifty generators de Ryan Bates. Esto significa que este texto también aparecerá en el título de la página. (Ya vimos este truco en el episodio 30 [verlo, leerlo].) También hemos cambiado la maquetación de la página para que los elementos de formulario se muestren como parte de una lista. Con algunos ajustes de estilo en la CSS podemos hacer que la etiqueta de cada campo aparezca al lado del campo propiamente dicho.

La
hoja de formulario personalizada.

El resto de las vistas se puede modificar de forma similar para que su aspecto se ajuste al del resto de nuestra aplicación.

Personalización de los mensajes de error

En Devise existen ciertos mensajes de error que aparecen cuando algo va mal. Por ejemplo si se introduce una clave o dirección de correo incorrecta veremos el mensaje “Invalid email or password”. Todos estos mensajes están almacenados en un archivo de internacionalización lo que hace que sa sencillo cambiarlos o traducirlos al idioma que queramos. Nosotros hemos cambiado el mensaje devise.failure.invalid.

/config/locales/devise.en.yml

en: 
  errors: 
    messages: 
      not_found: "not found" 
      already_confirmed: "was already confirmed" 
      not_locked: "was not locked"

  devise: 
    failure: 
      unauthenticated: 'You need to sign in or sign up before continuing.' 
      unconfirmed: 'You have to confirm your account before continuing.' 
      locked: 'Your account is locked.'
      invalid: 'OH NOES! ERROR IN TEH EMAIL!' 
      invalid_token: 'Invalid authentication token.'
      timeout: 'Your session expired, please sign in again to continue.' 
      inactive: 'Your account was not activated yet.' 
    sessions: 
      signed_in: 'Signed in successfully.' 
      signed_out: 'Signed out successfully.' 
#rest of file omitted. 

Si ahora introducimos una dirección de correo electrónico incorrecta se muestra el mensaje modificado.

Mensaje de error personalizado.

Con esto cubriríamos los mensajes de error, ¿pero qué pasa con las validaciones de Devise, por ejemplo cuando alguien introduce datos incorrectos cuando se da de alta?

Los mensajes de validación por defecto de Devise.

En el directorio /config/initializers de la aplicación hay un fichero llamado devise.rb y este fichero contiene muchas opciones de configuración de Devise. Están bien documentadas por lo que es fácil averiguar cuáles son las que tenemos que modificar si queremos hacer cambios. Así, si queremos reducir el tamaño mínimo de caracteres de la clave de cuatro a seis caracteres sólo tenemos que descomentar la última línea del archivo y hacer el cambio necesario. Nótese que tendremos que reiniciar el servidor después de hacer nuestros cambios.

/config/initalizers/devise.rb

  # ==> Configuration for :validatable 
  # Range for password length 
  # config.password_length = 6..20 

Podemos ir un paso más lejos cambiando las validaciones por defecto por las nuestras. Si miramos en nuestro model User veremos una lista de los módulo de Devise que estamos utilizando en nuestra aplicación, y uno de ellos es :validatable.

/app/models/user.rb

  class User < ActiveRecord::Base 
    # Include default devise modules. Others available are: 
    # :token_authenticatable, :lockable, :timeoutable and :activatable 
    # :confirmable, 
    devise :database_authenticatable, :registerable, 
           :recoverable, :rememberable, :trackable, :validatable

    # Setup accessible (or protected) attributes for your model 
    attr_accessible :email, :password, :password_confirmation 
  end 

El módulo :validatable controla la validación del correo y la clave cuando nos damos de alta. Si queremos cambiar dicho comportamiento podemos eliminar el módulo y gestionar las validaciones por nuestra cuenta. Nosotros no lo cambiaremos porque los valores por defecto del módulo :validatable son razonablemente buenos en la mayoría de los casos.

Rutas

A continuación personalizaremos las rutas. Por defecto la página de registro se encuentra en /users/sign_up pero queremos que sea /register. En el episodio anterior, Cuando ejecutamos el generador de Devise creó una ruta llamada devise_for :users. Esta ruta acepta algunos parámetros con los que podemos modificarla.

/config/routes.rb

  ProjectManage::Application.routes.draw do |map| 
    devise_for :users

    resources :projects 
    root :to => 'projects#index' 
  end

Uno de estos parámetros es :path_names con el que podemos cambiar la ruta de nuestra página de registro.

/config/routes.rb

  ProjectManage::Application.routes.draw do |map| 
    devise_for :users, :path_names => { :sign_up => "register" }

    resources :projects 
    root :to => 'projects#index' 
  end

Tras hacer este cambio veremos un error de rutas cuando visitemos /users/sign_up, ahora tendremos que visitar /users/register. En la documentación de Devise aparecen todas las opciones que acepta el método devise_for.

Ajustes en los requisitos de inicio de sesión

Nuestra aplicación utiliza una combinación de correo electrónica y clave para que los usuarios inicien la sesión pero los cambios para que pregunte un nombre de usuario en lugar del correo son fáciles:

Lo primero que tenemos que hacer es crear una columna username en la tabla User, de lo que por supuesto se encargará una migración.

$ rails generate migration add_username_to_users username:string

Con esto podemos ejecutar la migración,

$ rake db:migrate 

Dado que sólo tenemos un usuario en la base de datos podemos entrar en la consola de Rails y configurar un valor para el atributo username de este usuario.

$ rails c 
Loading development environment (Rails 3.0.0.beta2) 
ruby-1.8.7-p249 > User.first.update_attribute(:username, "eifion") 
 => true 

Ahora tenemos que modificar el archivo de configuración de Devise, descomentando la línea que comienza por config.authentication_keys y cambiando el valor de :email a :username.

/config/initializers/devise.rb

config.authentication_keys = [:username ]

Con esto Devise utilizará como clave de autenticación el campo username. También tendremos que hacer cambios en el formulario de inicio de sesión para que solicite el nombre de usuario en lugar del correo electrónico.

/app/views/devise/sessions/new.html.erb

<% title "Sign In" %>

<%= form_for(resource_name, resource, :url => session_path(resource_name)) do |f| %> 
  <ol class="formList"> 
    <li><%= f.label :username %> <%= f.text_field :username %></li> 
    <li><%= f.label :password %> <%= f.password_field :password %></li>
    <% if devise_mapping.rememberable? -%> 
    <li><%= f.check_box :remember_me %> <%= f.label :remember_me %></li> 
    <% end %> 
    <li><%= f.submit "Sign in" %></li>
  </ol> 
<% end %>

<%= render :partial => "devise/shared/links" %>

También habrá que modificar el formulario de registro y añadir una validación al modelo User pero eso queda como ejercicio para el lector.

Tras reiniciar el servidor (para que surtan efecto los cambios de la configuración) podemos visitar la página de inicio de sesión y entrar con un nombre de usuario en lugar de la dirección de correo.

Ahora podemos iniciar la sesión con un noombre de usuario.

Esto es todo por este episodio en el que hemos visto cómo hacer cambios en la configuración de Devise.