-
Notifications
You must be signed in to change notification settings - Fork 13
paso a paso
mostrar la aplicación terminada
http://jugar-demo.appspot.com/
http://jugardemo.herokuapp.com/login
-
descargar e instalar play
-
crear un proyecto vacío
-
saludador - mostrar cómo play resuelve un request
-
modelos - soporte para JPA
-
cargar los modelos - sql manager integrado y módulo de scaffolding
-
validaciones
-
fixtures
-
accion y template para borrar un evento
-
customizando las urls de las acciones
-
acciones y templates para modificar y dar de alta un evento
-
combo de tipos de eventos
-
pruebas unitarias, funcionales y de aceptación (selenium)
-
jugar con la aplicación real
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/
objetivo: mostrar la estructura de una aplicación de Play
- crear el proyecto
cd ~/play
play new jugar-demo
cd jugar-demo
play run
-
navegar a http://localhost:9000/
-
mostrar la página de bienvenida http://localhost:9000/
-
mostrar la documentación local http://localhost:9000/@documentation
-
mostrar los javadocs http://localhost:9000/@api/index.html
-
mostrar y explicar la estructura de directorios
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
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
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!
objetivo: cargar información mediante la consola integrada y el módulo crud
5.1. - consola integrada
- navegar a http://localhost:9000/@db
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;
- refrescar http://localhost:9000/
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
- 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
-
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");
}
}
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
-
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
- 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
- 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.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}
- 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}
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