sisè / segona

Soy Víctor Rodríguez, programador web freelance con más de 10 años de experiencia

Definir página de inicio por rol de usuario con Ruby on Rails 4

Para una pequeña aplicación que estoy desarrollando en Ruby on Rails me ha surgido la necesidad o más bien las ganas de crear una página de inicio según el rol del usuario que esté identificado en el sistema. La idea es montar un dashboard en el futuro, y claro, lo que necesita un administrador no es lo que necesita un cliente del sistema. Así que había dos opciones, creas un controlador con una única acción que determine qué mostrar al usuario según su rol, o algo más elegante, le dejas decidir a Rails qué acción y controlador debe llamar de inicio. De esta manera, puedes separar mucho mejor la lógica de negocio de un usuario administrador y la de un usuario cuyo rol sea el de cliente.

Premisa: ¿qué queremos conseguir?

Algo tan sencillo como que la aplicación muestre una u otra información dependiendo del tipo de usuario de sesión. Si el usuario que se identifica en el sistema es un administrador, que pueda ver los nuevos pedidos que han creado los clientes del sistema. Y que si el usuario que se identifica es un cliente, que pueda ver el estado en el que están los últimos pedidos que ha realizado en el sistema. Por poner un ejemplo.

¿Cómo lo conseguimos?

Como sabrás, para definir la página de inicio o root en nuestra aplicación Ruby on Rails necesitas algo así dentro del fichero routes.rb

root to: "home#index"

Donde ‘home’ hace referencia al controlador HomeController e ‘index’ a la acción o función index que hay en él. Hasta aquí todo muy fàcil. ¿Pero si queremos distinguir entre varios tipos de usuarios como montamos más de una página root?

Aquí entran en juego las llamadas constraints (traducción: restricciones) que se pueden añadir a cualquier ruta definida en Rails.

Hay varias maneras de definir las restricciones con Rails, las podéis encontrar en la guía (siempre visitad la guía de Rails) a partir de aquí, en la sección de Rails Routing from Outside In de las guias.

La que nos interesa es la apuntada en el apartado 3.10. Advanced Constraints.

En concreto, determinaremos vía lambda si el usuario tiene el rol de administrador o de cliente en la restricción y Rails hará el resto del trabajo por nosotros. Parece mágia… Pero bueno, pongamos el código y lo explicamos después.

root to: 'home#index', constraints: lambda { |request| !request.env['warden'].user }

root to: 'admin/dashboard#index', as: 'admin_root',
  constraints: lambda { |request| request.env['warden'].user.administrator? }

root to: 'customer/dashboard#index', as: 'customer_root',
  constraints: lambda { |request| request.env['warden'].user.customer? }

Expliquemos un poco el código.

¿Qué ha pasado aquí?

La primera ruta, mira si en el sistema no existe un usuario en sesión, y llama a la acción index del HomeController. Necesaria cuando no hay nadie identificado, lógico.

La segunda, mira si el usuario es adminsitrador. En caso afirmativo, llama a la acción index del controlador DashboardController dentro del módulo admin.

'admin/dashboard#index'

Y la tercera, lo mismo pero para usuarios cliente. En este caso llama a la acción index del controlador DashboardController pero dentro del módulo customer.

'customer/dashboard#index'

Un poco más en detalle, vemos que la lambda recibe como parámtetro el objeto ActionDispatch::Request. Es en este objeto donde miramos si existe o no el usuario en sesión. Para ello, debes saber que estoy usando la librería Devise, construida sobre una librería llamada Warden.

Como vemos en su documentación, los usuarios se guardan dentro del entorno ‘warden’. Tal que así:

env['warden'].user

Esta simple línea nos da acceso al usuario en sesión. Ahora solo falta preguntarle si es administrador o cliente.

Lo podríamos haber hecho de la siguiente manera, accediendo directamente al atributo role de la clase User y comparándolo con los posibles valores, ‘administrator’ o ‘customer’.

env['warden'].user.role == 'administrator'
env['warden'].user.role == 'customer'

Pero eso requier saber demasiadas cosas sobre la clase usuarios fuera de ella, así que mejor implementar un par de funciones dentro de la clase User que nos respondan más directamente. El código queda mucho más limpio y fácil de mantener.

class User < ActiveRecord::Base
  
  def administrator?
    self.role == 'administrator'    
  end
  
  def customer?
    self.role == 'customer'
  end

end

Eso es todo, hace falta meterse a investigar las entrañas de Devise y Warden para llegar a esta solución, pero es realmente efectiva cuando la conoces.

:P

UPDATE: dejo aquí un enlace con un artículo relacionado Role based routing in Rails

Rails Rumble 2013: Digestly

Este año he participado en la Rails Rumble 2013. Un concurso de programación en equipos de hasta 4 personas usando Ruby (mayormente on Rails) para crear una aplicación en 48 horas.

Este año tubo lugar los días 18 y 19 de Octubre, y aunque el concurso no es localizdo, me fui a Madrid para concursar con Alberto Molpeceres, Aitor Izquierdo y Mark Villacampa.

Aunque no se en qué meritoria posición quedó el equipo (mad13), el resultado fue digestly.

Digestly es una herramienta de ayuda para la difusión de enlaces de interés mediante newsletters. Y ha sido desarrollada con Ruby on Rails 3.2 y Delayed Job, Cron tasks, Mandrill, HTML, Bootstrap, Mysql, Apache+Passenger entre otras gemas.

Tengo que decir que a pesar de lo cansado que es despertarse un sábado a las 5 de la mañana y volver a casa el domigo a las 23 horas, la experiencia ha sido muy agradable y divertida. Me ha servido para pelearme de una vez con una aplicación Rails de verdad y aprender un montón.

La idea es seguir el desarrollo de la aplicación y evolucionarla para dar un servicio más completo. ¿Por qué no la pruebas?