Play!1 …mas o menos completo (5)


Qué es lo siguiente?
Repasamos conceptos:
– Models (+JPA)

– Validaciones

Models (y JPA)

El Modelo es la última parte a entender en la arquitectura MVC.
Todas las clases model son guardadas en el directorio app/models.

Función

Como parte del modelo MVC es del núcleo de la aplicación. Éste contiene la representación de los datos de tu aplicación y la lógica de negocio para hacer las “funciones propias de una aplicación” (o algo así mal traducido, se entiende…). Como se escribió en posts anteriores, el propósito de la vista es renderizar el modelo y el propósito del controlador es actuar controlando la interface para correr acciones en el modelo de datos desde usuario.

Mientras que un framework no fuerza a que dejes de poner lógica de negocios en el controlador y usar el modelo como un objeto simple de acceso a los datos, los framework advierten que el Model es usado para ambas cosas, los datos y lógica de negocios. O sea: haga como quiera y va a funcionar, pero si desea trabajar correctamente haga como lo recomiendan los que saben.

Creando un Data Model (ó, Modelo de Datos)

Crear un Data Model es tremendamente fácil. Un data model en play es simplemente un set de clases de Java que son guardados en app/models. Pero hay algunas diferencias que necesita saber.
– las propiedades del modelo deben ser public (a eso agreguemos que no sea static ni final)
– No necesitas crear setters ni getters
Para los programadores Java con experiencia esto sonará raro y muy equivocado. No se preocupe, detrás de escena Play creará los setters y getters por usted. Play sugiere que con ello incrementa en algo la productividad. (se hablará más adelante)
Un ejemplo de un User puede ser:

package models;
 import play.libs.Codec;
 public class User {
 public String email;
 public String passwordHash;
 public String name;
 public boolean admin;
 public User(String email, String password, String name) {
 this.email = email;
 this.passwordHash = Codec.hexMD5(password);
 this.name = name;
 this.admin = false;
 }
 }

Sus futuras aplicaciones web fácilmente tendrán muchas clases que conformen el Modelo, pero para ser claros trabajaremos ejemplos con esas clases bien simples.

Persistencia en Bases de Datos usando JPA

Persistir en bases de datos con Play es una tarea extremadamente sencilla. Play hace uso de JPA (la API de Persistencia de Java) para guardar datos de tu modelo a la database en unas pocas líneas de código. De hecho, es de esperar que Play haga uso del estándar (y lo hace).
No hace falta preocuparse acerca del entity manager para guardar, borrar, actualizar o hacer consultas a la base de datos. Play abstrae todo eso por ti con algunos métodos disponibles en la clase model para tener tu aplicación corriendo inmediátamente.

Antes de seguir JPA en detalle, lo primero que debemos hacer es setear la base de datos, o algún lugar donde vayamos a guardar.

Seteando una Base de Datos
Play viene con un número de opciones que permiten dejar corriendo una base de datos de forma inmediata. Una vía rápida es usar una base de datos en memoria o incluso una base de datos basada en archivos. No es una solución permanente o una opción para un entorno productivo, pero permite empesar a usar una database sin tener instalado siquiera MySQL.
Por defecto,  una nueva aplicación de play no preconfigura la base de datos a usar, pero trae comentado un buen número de ejemplos.
El archivo de configuración es conf/application.cong. La parte que debemos ver es

 # Database configuration
 # ~~~~~
 # Enable a database engine if needed.
 # There are two built in values :
 #   - mem : for a transient in memory database (HSQL in memory)
 #   - fs  : for a simple file written database (HSQL file stored)
 #
 db=mem

La propiedad db es responsable de configurar la base de datos requerida. Aquí puedes ver que se usa la base de datos en memoria. HSQLDB (HyperSQL Java Database) es una base de datos open source que corre en memoria.
Es recomendable usar ésta porque es lo más rápido y fácil para empezar, y es prácticamente lo mismo que otra base de datos. Solo tienes que dejarlo descomentado (sin #).
Así mismo puedes ver que también figura MySQL o cualquier base con JDBC. Cestión de leer, descomentar y completar los campos (url, driver, user, pass).

Uso de JPA

Por debajo, Play usa Hibernate como implementación de JPA. Para convertir tu modelo en un modelo persistente necesitas agregar algunas líneas de código.
Volviendo a la clase User que se empezó en post anteriores, solo necesitamos hacer 2 cambios a la clase original para que tenga persistencia (incluir la anotación y extender de jpa)

package models;
 <span style="color: #008000;">@javax.persistence.Entity</span>
 public class User <span style="color: #008000;">extends play.db.jpa.Model</span>{
 public String email;
 public String passwordHash;
 public String name;
 public boolean admin;
 …
 }

Esas dos cosas:
– agregando la anotación @javax.persistence.Entity especificamos que la clase es una entidad JPA, y por eso ahora puede guardar datos.
– el segundo cambio hace que extienda de play.db.jpa.Model para hacer las cosas propias de CRUD.

Algunos de esos métodos se muestran aquí.

// find user
 User user = User.findById(1L);
 // update user email
 user.email = “notengo@email.com”;
 // update database
 user.save();
 …
 // delete user
 user.delete();
 …
 // find all users with certain criteria
 List<User> users = User.find(“admin = ?”, “true”).fetch();

Por ahora el objetivo solo es introducir este concepto.

Acceso directo a la base de datos

Nosotros deberíamos lograr hacer la mayoría de las cosas a través de JPA. Play accede através de la clase play.db.DB
Algunos de los métodos que se pueden usar para hacer consultas a la base de datos:

  • getConnection, te da acceso a el objeto Connection, desde el que creas tus consultas sql.
  • executeQuery, es un método que ejecuta un query en la conexión a la base de datos y retorna un objeto ResultSet.
  • execute, para ejecutar un update, retorna un booleano para informar si fue exitoso.

Play Cache

Cómo persistir datos? A los datos por lo general los guardamos de varias formas pero para mejorar la performance y tenerlos a mano podemos mantenerlos en caché.
Play tiene dos implementaciones de caché. Si está configurada en application.conf la memcached, Play usará ésta. Sinó usará la EhCache incluída para guardar el caché en el servidor.
Cuando escales la aplicación y deployes en múltiples servers, no puedes garantizar que los datos estén de request a request porque puedes pasar entre servidores. Pero esto es parte de la escencia de Play de la arquitectura ‘share nothing’.

Setters y Getters

Ya dijimos que con la clases Model los atributos van a ser privados y no habrá setters ni getters. La razón es acelerar el desarrollo. Seguro habrás notado que Play convierte las clases en tiempo de ejecusión volviendo los atributos a privado y crea setters y getters.
La razón principal para crear atributos privados y hacer setters y getters, es que esto fuerza mejores prácticas para encapsulación. Encapsulación es parte del núcleo de Java y programación Orientada a Objetos, y Play no saltea eso. Justamente reconoce eso que tan tedioso de crear setters y getters para los atributos privados, cuando el 95% del tiempo simplemente devuelve una variable sin tocar.
Si queremos crear setters y getters para los atributos (aunque debemos mantener los atributos como public) podemos hacerlo. En tiempo de ejecución, Play chequeará para ver si los setters y getters ya existen.

Validaciones

Función

En la mayor parte de las aplicaciones web vamos a necesitar tener cierto nuvel de validación en los parámetros que los controladores reciben como parte de un evento. Play tiene un número de opciones para hacer validaciones en las entradas del usuario y devuelve mensajes al navegador.

Uso Básico

La forma más fácil para entender como la validación ayuda a trabajar en una clase es a través de ejemplos.
Entonces un registro de usuario en el controlador podría ser así:

public static void register(String user, String email, String password) {
 // checkear que el usuario ha sido suministrado
 validation.required(user);
 // chekear que la dirección de email es válida
 validation.email(email);
 // checkear que el password tenga entre 6 y 10 caracteres
 validation.minSize(password, 6);
 validation.maxSize(password, 10);
 …
 render();
 }

No hace falta aclarar que hicimos en cada renglón…
Si cualquiera de las rutinas de validación falla, un error va a ser agregado a la lista de errores.

En fin, podrías acceder a la lista de errores en la validación del objeto directamente haciendo esto:

public static void register(String user, String email, String password) {
 // checkea si dieron el usuario
 if (user == null || user.length() == 0) {
 validation.addError(“user”, “Debe ingresar el nombre de usuario”);
 }
 …
 }

A veces que las validaciones se vuelven complejas, pero las validaciones ayudan a tus métodos tener soluciones mucho más limpias, fáciles de leer y asegurar consistencia a la aplicación de Play, haciendo todo más mantenible.

Mensajes por defecto

Por ahora en el ejemplo usamos los mensajes por defecto que provienen del archivo de mensajes (message).
Cuando usamos los metodos de validación, Play automáticamente crea un mensaje por defecto dependiendo del tipo de método de error usado.
Por ejemplo, si queremos hacer una validacion del parámetro de usuario, Play buscará el mensaje “validation.required” en el archivo de mensajes. Puedes crear algo así:

validation.required=Debe entrar el valor para %s

El %s va a reemplazarse por el nombre de la variable que falle la validación. Así, todos los métodos validation.required serán usados para el mismo mensaje de error de validación (claro que todo puede customizarse).

Custom Messages

Para poner un mensaje personalizado a una rutina de validación tenemos que agregarlo al método después de la llamada a la validación .message(“…”);

public static void register(String user, String email, String password) {

// checkear si ha sido dado el usuario
 validation.required(user).message(“Ingrese su nombre de usuario”);

// checkear si el email es válido
 validation.email(email).message(“El email es inválido”);

// checkear si el password tiene entre 6 & 10 caracteres
 validation.minSize(password, 6).message(
 “El password debe tener al menos 6 dígitos”);
 validation.maxSize(password, 10).message(
 “El password debe tener nomás de 10 dígitos”);
 … }

Los mensajes pueden ser texto plano o podrían ser una clave para acceder a un archivo de mensajes.

Mostrando mensajes en la Vista

La validación de parámetros no es suficiente para permitir saber al usaurio que está haciendo algo mal, hay que mostrar el error al usuario para que sea conciente algo no fue del todo bien.
Play provee algunos tags que ayudan a mostrar mensajes de error en la vista.

  • #{ifErrors}, que nos deja ver el error donde fue encontrado por el controlador. El #{else} podríamos usar para mostrar diferentes resultados dependiendo si había error o no. (suena confuso, pero a esta altura pero podríamos usar un poco la imaginación sobre qué podríamos hacer usando un else, algo obvio).
  • #{errors}, itera sobre la lista de errores para mostrarnos una lista sobre lo que encontró
  • #{error ‘fieldname’/}, permite especificar el mensaje de error para algo específico. Es útil si queremos mostrar un mensaje de error luego del elemento del formulario en donde se originó el error.

Ejemplo usando los tags:

#{ifErrors}
 <h1>Errores Encontrados</h1>
 <p>Pocesando su petición de registro encontramos errores. </p>
 <ul>
 #{errors}
 <li>${error}</li>
 #{/errors}
 </ul>
 <p>Please go back and check the details and try again</p>
 #{/ifErrors}
 #{else}
 The registration process was successful!
 #{/else}

Volver hacia el formulario de envío

En el ejemplo previo, el controlador renderiza la vista por defecto, que es la misma página y no tiene en cuenta si en donde aparecieron los errores o no. Necesitaríamos volver atrás hasta el formulario de donde vienen los errores.
Hay algunos pasos extra que necesitamos hacer para que esto funcione. Cuando redireccionamos, Play envía una solicitud devuelta al browser para decirle que cargue el URL redireccionado y asegure el estado siempre consistente con la URL solicitada. Por eso, cuando la página es renderizada los errores se van a perder porque empezamos una nueva acción, y el objeto de validación se creó por cada nueva acción, a menos que especifiquemos un request para mantener disponibles los errores mediante una request extra.
Para hacer esto necesitamos cambiar nuestro controlador y trabajar de la siguiente forma:

public static void register(String user, String email, String password){

//checkear que dieron el usuario
 validation.required(user);

//chekear que la dirección de mail es válida
 validation.email(email);

//checkear que el password del usuario esté entre 6 y 10 caracteres.
 validation.minSize(password, 6);
 validation.maxSize(password, 10);

//volver al formulario de registro si hay errores
 if(validation.hasErrors()) {
 params.flash();
 validation.keep();
 registrationForm();
 }
 // si no hay errores, mostrar la página que se ha completado con éxito
 render();
 }

Necesitaríamos cambiar nuestra página registrationForm para incluir los tags ifErrors y mostrar los mensajes como una lista o en cada ítem del formulario. Para mostrar cada item la página quedaría parecida a esto:

#{ifErrors}
 <h1>Se Encontraron Errores</h1>
 #{/ifErrors}

#{form @Application.register()}
 <div>
 Nombre: <input type=”text” name=”user” value=”${flash.user}” />
 <span class=”error”>#{error ‘user’ /}</span>
 </div>
 <div>
 Email:<input type=”type” name=”email” value=”${flash.age}” />
 <span class=”error”>#{error ‘email’ /></span>
 </div>
 <div>
 Password: <input type=”text” name=”password” value=”${flash.password}” />
 <span class=”error”>#{error ‘password’ /}</span>
 </div>
 <div> <input type=”submit” value=”Register” /> </div>
 #{/form}

Errores Anidados (Nesting Errors)

Esto puede corresponder a, si por ejemplo, queremos primero verificar la clave sólo si primero ingresó correctamente el nombre de usuario.
Para hacer esto podemos usar el atributo ok en el resultado de la validación.

if (validation.required(user).message(“Por favor ingrese el usuario”)<span style="color: #003366;">.ok</span>) {
 validation.minSize(user, 6).message(
 “El usuario debe tener al menos 6 caracteres.”);
 validation.maxSize(user, 20).message(
 “El usuario no debe superar los 20 caracteres.”);
 }

Usando Anotaciones

Mas que usar una clase que ayude a validar, Play ofrece otra vía para validar el contenido de datos que se pasan al controlador, usando anotaciones.
Para cada validación hay un método, una anotación equivalente. Así, para el ejemplo de registro podríamos hacerlo así:

public static void register(@Required String user, @Email String email,
 @MinSize(6) @MaxSize(10) String password) {
 // volver al formulario de registro si hay errores
 if(validation.hasErrors()) {
 params.flash();
 validation.keep();
 registrationForm();
 }
 // si no hay errores renderizar la página siguiente
 render();
 }

Objeto Anotaciones

Las anotaciones son muy prácticas para validación de parámetros individuales, podemos pasar objetos como parámetros en Play. Es posible agregar anotaciones de validación a la clase del modelo, y de pasar objetos como parámetros ahora usando anotaciones.
Para esto simplemente usamos la anotación @Valid para el objeto pasado como parámetro, indicando que queremos validar el objeto, a continuación uso las anotaciones de validación que vimos para validar por separado los atributos de el objeto model.
El controlador quedaría similar a esto:

public static void register(@Valid User user){
 …
 }

La clase model User quedaría así:

public class User {
 @Required
 public String user;
 @Email
 public String email;
 @MinSize(6)
 @MaxSize(10)
 public String password;
 …
 }

Validaciones Personalizadas

Podemos crear nuestra propia validación usando la anotación @CheckWith que usa una clase para validación.
Vemos un ejemplo que valide un password que exija incluir números y caracteres.

public class User {
 @Required
 @CheckWith(ChequeaLetrasPass.class)
 public String password;

static class ChequeaLetrasPass extends Check {
 public abstract boolean isSatisfied(Object user, Object password){
 return contieneLetrasYNumeros(password);
 }
 }
 }

La forma que esto trabaja es que el @CheckWith especifica una clase que presenta la rutina que vamos a usar. La clase debe extender de play.data.valitation.Check e implementar el método isSatisfied. Los objetos son: la clase que contiene el atributo que va a ser validado y el atributo mismo.
Simplemente devueve un booleano en true si la validación pasó, sinó un false.

@wfranck

One response to this post.

  1. Que buen laburo te mandaste Walter. Gracias.

    Reply

Leave a Reply

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

WordPress.com Logo

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

Twitter picture

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

Facebook photo

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

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: