Skip to content
opensas edited this page Sep 29, 2011 · 26 revisions

Demo de play paso a paso

Introducción

objetivo de la demo

mostrar la aplicación terminada

http://jugar-demo.appspot.com/

http://jugardemo.herokuapp.com/login


  1. descargar e instalar play

  2. crear un proyecto vacío

  3. saludador - mostrar cómo play resuelve un request

  4. modelos - soporte para JPA

  5. cargar los modelos - sql manager integrado y módulo de scaffolding

  6. validaciones

  7. fixtures

  8. accion y template para borrar un evento

  9. customizando las urls de las acciones

  10. acciones y templates para modificar y dar de alta un evento

  11. combo de tipos de eventos

  12. pruebas unitarias, funcionales y de aceptación (selenium)

  13. jugar con la aplicación real

1. descargar e instalar play

mkdir ~/play
cd ~/play
wget http://download.playframework.org/releases/play-1.2.3.zip
unzip play-1.2.3.zip

-- agregar el directorio de play al path
PATH="~/play/play-1.2.3:$PATH"

--copiar plugin de eclipse
cp ~/play/play-1.2.3/support/eclipse/org.playframework.playclipse_0.7.0.jar <eclipse install>/dropins/

2. crear un proyecto vacío

objetivo: mostrar la estructura de una aplicación de Play

  • crear el proyecto
cd ~/play
play new jugar-demo
cd jugar-demo
play run
public
conf
  application.conf
  routes
  dependencies.yml
app
  models
  controllers
  views
    main.html
    Application
      index.html
  • mostrar y explicar cómo play llega a mostrar la página de bienvenida

  • explicar el patrón model-view-controller

  • mostrar conf/routes

  • mostrar controllers.Application.index

  • mostrar views/Application/index.html

  • mostrar views/main.html

3. saludador

objetivo: mostrar cómo play! nos permite trabajar dinámicamente con nuestra aplicación

  • editar Application.index.html
Hola ${name}
  • refrescar el explorador, mostrar que toma los cambios automaticamente

  • editar Application.java con un editor de texto

    public static void index(String name) {
        render(name);
    }
  • introducir un error en Application.java
  • asignar un valor por defecto a name
  • no poner el ";" al final
    public static void index(String name) {
	if (name==null) name = "amigo desconocido"
        render(name);
    }
  • mostrar como play reporta el error
  • arreglar el error y refrescar

4. modelos

objetivo: mostrar cómo trabajar con Eclipse y el soporte de play para JPA

  • frenar el servidor - ctrl-C

  • play eclipsify

  • explicar este mensaje

    ~ Use eclipsify again when you want to update eclipse configuration files. ~ However, it's often better to delete and re-import the project into your workspace since eclipse keeps dirty caches...

  • importar en eclipse

  • mostrar el plugin de eclipse

  • iniciar la aplicación desde eclipse

  • pararse en la carpeta models, y hacer click en el ícono de model del plugin de play

  • crear EventType

  • crear EventType model creando una nueva clase (para mostrar que el plugin no hace gran cosa...)

  • no olvidarse de las anotaciones @Entity y @ManyToOne

models.Event

@Entity
public class Event extends Model {
    
	public String name;
	
	@ManyToOne
	public EventType type;
	
	public String place;
	
	public Date date;
	
}

models.EventType

@Entity
public class EventType extends Model {
    
	public String name;
	
}
  • Modificar el controlador

  • remplazar el método index por el método list

controllers.Application.list

    public static void list() {
    	List<Event> events = Event.find("order by date desc").fetch();
        render(events);
    }
  • renombrar Application/index.html a Application/list.html, y modificar el contenido

views/Application/list.html

#{if events}
	<table>
		<thead>
			<th>#</th>
			<th>Evento</th>
			<th>Tipo</th>
			<th>Lugar</th>
			<th>Fecha</th>
			<th></th>
		</thead>
	#{list events, as:'event'}
		<tr>
			<td>${event.id}</td>
			<td>${event.name}</td>
			<td>${event.type.name}</td>
			<td>${event.place}</td>
			<td>${event.date}</td>
			<td></td>
		</tr>
	#{/list}
	</table>
#{/if}
#{else}
¡No hay ningún evento!<br />
#{/else}
  • navegar a localhost:9000 (la aplicación ya estaba corriendo en eclipse)

  • mostrar los errores

    • No datasource configured -> * modificar application.conf
    • Application.index action not found -> * modificar routes file

conf/routes

# Home page
GET     /                                       Application.list
  • navegar a localhost:9000
  • mostar el mensaje: ¡No hay ningún evento!

5. cargar los modelos

objetivo: cargar información mediante la consola integrada y el módulo crud

5.1. - consola integrada

url jdbc connect: jdbc:h2:mem:play

  • ejecutar la siguiente sentencia sql
insert into eventtype( name ) values ( 'presentacion' );
insert into eventtype( name ) values ( 'workshop' );
select * from eventtype;

insert into event( name, type_id, place, date ) values ( 'primer evento', 1, 'lugar del primer evento', '2011-08-24 10:30:00' );
insert into event( name, type_id, place, date ) values ( 'segundo evento', 2, 'lugar del segundo evento', '2011-08-25 14:30:00' );
select * from event;

5.2. - módulo crud

  • frenar el proyecto

  • add crud module to dependencies.yml file

/conf/dependencies.yml

require:
    - play -> crud
  • play deps
~ Resolving dependencies using /home/sas/Dropbox/Public/devel/play/apps/events/conf/dependencies.yml,
~
~ Installing resolved dependencies,
~
~ 	modules/crud -> /home/sas/devel/opt/play-1.2.2/modules/crud
~
~ Done!
  • play eclipsify

  • refresh project in eclipse

  • mostrar cómo crud se ha integrado al projecto - un módulo tiene la misma estructura que una aplicación de play

  • arrancar el proyecto

  • creamos los controladores CRUD, explicar la convencion Event(s), EventType(s)

controllers.Events

package controllers;

import play.mvc.*;

public class Events extends CRUD {
}

controllers.EventTypes

package controllers;

import play.mvc.*;

public class EventTypes extends CRUD {
}
  • importamos las rutas de crud en el archivo routes

conf/routes

*		/admin									module:crud
  • navegamos a localhost:9000/admin

  • damos de alta los eventos presentacion y workshop

  • implementamos toString en EventType y Event

6. validaciones

  • agregamos los campos requeridos @Required en el modelo, modificar el mensaje de error

models.Event

package models;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.ManyToOne;

import play.data.validation.Required;
import play.db.jpa.Model;

@Entity
public class Event extends Model {

    @Required(message="debe ingresar el nombre del evento")
    public String name;

    @Required(message="debe seleccionar el tipo de evento")
    @ManyToOne
    public EventType type;

    @Required(message="debe ingresar el lugar del evento")
    public String place;

    @Required(message="debe ingresar la fecha del evento")
    public Date date;

    @Override
    public String toString() {
        return name;
    }
	
}

models.EventType

package models;

import javax.persistence.Entity;

import play.data.validation.Required;
import play.db.jpa.Model;

@Entity
public class EventType extends Model {

    @Required(message="debe ingresar el nombre del tipo de evento")
    public String name;

    @Override
    public String toString() {
        return name;
    }
	
}
  • modificamos el formato de fecha en application.conf - prestar atención al formato!!!

date.format=dd-MM-yyyy HH:mm

7. fixtures

  • hablar acerca de las ventajas de un fixture, para desarrollar y principalmente para testear

  • crear el archivo conf/data.yml

  • explicar cómo funciona, ejecuta el databinder

conf/data.yml

EventType(presentacion):
   name: presentacion

EventType(workshop):
   name: workshop

Event(uno):
   name: ¿Cómo afecta la nube a la integracion de Applicaciones?
   type: presentacion
   place: 25 de Mayo 555 piso 9, Oficina de MuleSoft
   date: 19-05-2011 18:30:00
   
Event(dos):
   name: Drools y jBPM5
   type: presentacion
   place: Reconquista 945, Hotel Melia
   date: 17-06-2011 9:00:00

Event(tres):
   name: Search Engines
   type: presentacion
   place: 25 de Mayo 555 piso 9, Oficina de MuleSoft
   date: 23-06-2011 18:30:00


Event(cuatro):
   name: ¿Por qué otros lenguajes en mi JVM?
   type: presentacion
   place: 25 de Mayo 555 piso 9, Oficina de MuleSoft
   date: 04-08-2011 18:30:00

Event(cinco):
   name: Escala el Everest con Scala
   type: presentacion
   place: 25 de Mayo 555 piso 9, Oficina de MuleSoft
   date: 04-08-2011 20:00:00

Event(seis):
   name: Java 7. ¿Qué hay de nuevo viejo?
   type: presentacion
   place: Costa Rica 5546, Zauber Software
   date: 25-08-2011 18:30:00

Event(siete):
   name: Play! framework, un framework hecho en java.. para herejes
   type: workshop
   place: Ingeniero Butty 240 6º piso, Globant
   date: 29-09-2011 20:00:00
  • crear el job que cargará la información

jobs.BootstrapJob

package jobs;

import play.jobs.Job;
import play.jobs.OnApplicationStart;
import play.test.Fixtures;

//@OnApplicationStart
public class BootstrapJob extends Job {

	@Override
	public void doJob() {
		Fixtures.deleteAllModels();
		Fixtures.loadModels("data.yml");
	}
	
}

8. accion para borrar un evento y volver a cargarlos desde el archivo yaml

objetivo: mostrar cómo crear e invocar acciones

8.1- borrar un evento

  • crear la acción Application.delete

controllers.Application.delete

    public static void delete(Long id) {
    	Event.delete("id = ?", id);
    	flash.success("el evento fue borrado");
    	list();
    }
  • explicar que list() hace un redirect

Views/Application/list.html

#{if flash.success}
	<h3>${flash.succes}</h3>
#{/if}

link para borrar

<td>#{a @Application.delete(event.id)}borrar#{/a}</td>
  • probarla, eliminar algunos eventos

  • agregar el link para volver a cargar el yaml

controllers.Application

    public static void loadFromYaml() {
    	new BootstrapJob().doJob();
    	list();
    }
  • agregar el link
#{a @loadFromYaml()}cargar desde el archivo yaml#{/a}
  • probarlo, eliminar algunos eventos, y volver a cargarlos

9. mejorar los urls del borrado de eventos y la recarga

  • navegar a la raíz y mostrar los urls del link borrar y load data from yaml file

  • mostrar el archivo routes

  • explicar la ruta catch all

  • comentar la ruta catch all, refrescar el explorador y mostrar el mensaje de error

  • arreglar los urls en el archivo routes

DELETE		/event/{id}				Application.delete
GET		/load					Application.loadFromYaml
  • refrescar la página y mostrar los url como quedaron

  • !!! descomentar la ruta catch all

10. acciones para modificar y agregar un evento

  • crear las acciones form y save

controllers.Action

	public static void form(Long id) {
		final Event event;
		if (id==null) {
			event = new Event();
		} else {
			event = Event.findById(id);
		}
		render(event);
	}

	public static void save(@Valid Event event) {
	    	if (validation.hasErrors()) {
	    		render("@form", event);
	    	}
	    	event.save();
	    	flash.success("Los cambios han sido guardados con éxito");
	    	list();
	}
  • crear el template form.html

views/Application/form.html

#{extends 'main.html' /}
#{set title:'Events' /}

#{ifErrors}
<p>se han encontrado errores</p>
#{/ifErrors}

#{form @save()}

	<input type="hidden" name="event.id" value="${event.id}" />
	
	nombre: <input name="event.name" value="${event.name}" /> <br />
	<span>#{error 'event.name' /}</span><br /><br />
	
	tipo: <input name="event.type.id" value="${event.type?.id}" /> <br />
	<span>#{error 'event.type' /}</span><br /><br />
	
	lugar: <input name="event.place" value="${event.place}" /> <br />
	<span>#{error 'event.place' /}</span><br /><br />
	
	date: <input name="event.date" value="${event.date?.format('dd-MM-yyyy HH:mm')}" /> <br />
	<span>#{error 'event.date' /}</span><br /><br />
	
	<br />
	<input type="submit" value="Grabar cambios" /> or #{a @list()}cancel#{/a}

#{/form}
  • explicar el action del form -> explicar el '@@'

  • explicar que es lo mismo que <form action="@{save()}">

  • explicar reverse routes

  • explicar el autobinding por convención, los nombres de los input

  • probarla, dar de alta algunos eventos, modificar otros

11. combo de tipos de evento

  • agregar la lista de tipos de eventos

en Application.form y en Application.save

final List<EventType> types = EventType.find("order by name").fetch();

otra manera:

@Before
public static void loadEventTypes() {
    renderArgs.put("types", EventType.find("order by name").fetch());
}

views/Application/form.html

	tipo: 
	<select name="event.type.id">
		<option value=""${event.type==null ? ' selected' : ''}>--seleccione una opción--</option>
		#{list types, as:'type'}
		    <option value="${type.id}"${event.type?.id==type.id ? ' selected' : ' '}>${type.name}</option>
		#{/list}
	</select><br />
	<span>#{error 'event.type' /}</span><br /><br />

12. pruebas

12.1 pruebas unitarias

  • frenar el proyecto, arrancarlo en modo prueba, y mostrar la consola de pruebas

  • crear una prueba unitaria para probar BootstrapJob

test app

BootstrapJobTest.java

import java.util.List;

import models.Event;
import models.EventType;

import org.junit.Before;
import org.junit.Test;

import play.test.Fixtures;
import play.test.UnitTest;

public class BootstrapJobTest extends UnitTest {

	@Before
	public void deleteModels() {
		Fixtures.deleteAllModels();
	}
	
    @Test
    public void bootstrapJobTest() {
    	
    	assertEquals("There should be no event types", 0, EventType.count());
    	assertEquals("There should be no events", 0, Event.count());
    	
    	new jobs.BootstrapJob().doJob();
    	
    	assertEquals("There should be 2 event types", 2, EventType.count());
    	assertEquals("There should be 7 events", 7, Event.count());
    	
    	final List<Event> events = Event.find("order by date desc").fetch();
    	
    	final Event first = events.get(0);
    	
    	assertEquals("First event's name should be 'Play!...'", 
    			"Play! framework, un framework hecho en java.. para herejes",
    			first.name);

    	assertEquals("First event's type should be 'workshop'", 
    			EventType.find("name = ?", "workshop").first(),
    			first.type);

    	final Event last = events.get(events.size()-1);

    	assertEquals("Last event's name should be '¿Cómo afecta...'", 
    			"¿Cómo afecta la nube a la integracion de Applicaciones?",
    			last.name);

    	assertEquals("Last event's type should be 'presentation'", 
    			EventType.find("name = ?", "presentacion").first(),
    			last.type);
    	
    }

}

12.2 pruebas funcionales

  • explicar qué son las pruebas funcionales

  • crear una prueba funcional para probar la accion delete

DeleteEventTest.java

import models.Event;

import org.junit.Before;
import org.junit.Test;

import play.db.jpa.JPA;
import play.mvc.Http.Response;
import play.test.Fixtures;
import play.test.FunctionalTest;

public class DeleteEventTest extends FunctionalTest {

	@Before
	public void deleteModels() {
		Fixtures.deleteAllModels();
		Fixtures.loadModels("data.yml");
	}

    @Test
    public void deleteEventTest() {

    	final Long originalCount = Event.count();
    	final Event originalNextEvent = Event.find("order by date desc").first();
    	
        Response response = DELETE("/event/" + originalNextEvent.id); 
        assertStatus(302, response);
        assertHeaderEquals("Location", "/", response);
        
        assertEquals("There should be one less event", originalCount-1, Event.count());
        
        assertNotSame("The next event must have changed",
                originalNextEvent,
                Event.find("order by date desc").first()
        );

        final Event deletedEvent = Event.find("id = ?", originalNextEvent.id).first();
        assertNull("The event should have been deleted", deletedEvent);

    }
    
}

12.3 selenium test

  • crear una prueba de aceptación que verifique todo el proceso

EventCrud.test.html

#{fixture delete:'all', load:'data.yml' /}

#{selenium}

    // Open the home page, and check that no error occured
    open('/')
    assertNotTitle('Application error')

    clickAndWait('xpath=//a[text()="crear evento"]')

    clickAndWait('css=input[type="submit"]')

	assertTextPresent('se han encontrado errores')
	assertTextPresent('debe ingresar el nombre del evento')
	assertTextPresent('debe seleccionar el tipo de evento')
	assertTextPresent('debe ingresar el lugar del evento')
	assertTextPresent('debe ingresar la fecha del evento')
	
    type('event.name', 'nuevo evento')
	
    select( 'event.type.id', 'label=workshop')
	
    type('event.place', 'lugar del nuevo evento')
	
    # test date format
    type('event.date', '10-25-2011 18:30')
	
    clickAndWait('css=input[type="submit"]')

    assertTextPresent('se han encontrado errores')
    assertTextNotPresent('debe ingresar el nombre del evento')
    assertTextNotPresent('debe seleccionar el tipo de evento')
    assertTextNotPresent('debe ingresar el lugar del evento')
    assertTextPresent('Incorrect value')

    # test date format
    type('event.date', '25-10-2011 18:30')

    clickAndWait('css=input[type="submit"]')

    assertTextPresent('los cambios han sido grabados con éxito')
    assertTextPresent('nuevo evento')
	
	# edito el evento recién creado
	clickAndWait('xpath=//a[text()="nuevo evento"]')
	
	type('event.name', 'nuevo evento modificado')
	clickAndWait('css=input[type="submit"]')
	
    assertTextPresent('los cambios han sido grabados con éxito')
    assertTextPresent('nuevo evento modificado')
    
    # elimino el evento recién creado
    clickAndWait('xpath=//a[text()="nuevo evento modificado"]/../..//a[text()="borrar"]')

    assertTextPresent('el evento fue borrado')
    
    assertTextNotPresent('nuevo evento modificado')
	
#{/selenium}

FIN DE LA PRESENTACION

  • mostrar la aplicación terminada, dónde bajar el código, etc...

EventDelete.test.html

#{fixture delete:'all', load:'data.yml' /}

#{selenium}

    // Open the home page, and check that no error occured
    open('/')
    assertNotTitle('Application error')

    // Delete Play! framework event
    assertTextPresent('Play! framework, un framework hecho en java.. para herejes')
    
    clickAndWait('xpath=//a[text()="Play! framework, un framework hecho en java.. para herejes"]/../..//a[text()="borrar"]')

    assertTextPresent('el evento fue borrado')
    
    assertTextNotPresent('Play! framework, un framework hecho en java.. para herejes')

    // delete the next 6 events
	clickAndWait('xpath=//a[text()="borrar"]')    
	clickAndWait('xpath=//a[text()="borrar"]')
	clickAndWait('xpath=//a[text()="borrar"]')
	clickAndWait('xpath=//a[text()="borrar"]')
	clickAndWait('xpath=//a[text()="borrar"]')
	clickAndWait('xpath=//a[text()="borrar"]')

    assertTextPresent('¡No hay ningún evento!')
    
    clickAndWait('xpath=//a[text()="load from yaml"]')
    
    assertTextNotPresent('¡No hay ningún evento!')
    assertTextPresent('Play! framework, un framework hecho en java.. para herejes')
	
#{/selenium}

13. jugar con la aplicación real

bajarse la aplicación de github

cd ~/devel/apps
git clone [email protected]:opensas/play-demo.git
cd play-demo
play-run

y luego probar con

play-test

para traer una rama en particular

git checkout origin/13-deploy_to_heroku

para traer la rama y seguir la rama original

git checkout --track -b 13-deploy_to_heroku origin/13-deploy_to_heroku