Genexus 2da Ed
Genexus 2da Ed
Genexus 2da Ed
3
Prólogo a la 2da. Edición
Todo el equipo de Artech ha trabajado mucho en la versión X de GeneXus.
Ésta no es, simplemente, una versión más: representa un gran salto adelante en
muchos aspectos y, muy especialmente, en su capacidad de integración y de
extensión, ya sea por Artech o por la propia Comunidad GeneXus.
La Comunidad GeneXus ha participado entusiastamente en las pruebas de
las versiones CTP y está adoptando la primera versión liberada del producto,
utilizándolo y, en muchos casos, construyendo extensiones. En el XIX
Encuentro esperamos la exposición de muchas experiencias importantes.
En el año 2007 hemos encomendado a Daniel Márquez Lisboa (autor de
“GeneXus, Desarrollo Basado en el Conocimiento – Guía Práctica” editado por
Magró en el 2006) y a Cecilia Fernández, con una muy buena experiencia como
instructora en nuestros cursos, un libro introductorio sobre la Versión Rocha de
GeneXus. Ahora que la versión X es una importante realidad, hemos pedido a
Daniel y Cecilia una segunda edición del libro, actualizando aquella historia,
contada sobre la marcha del desarrollo de la versión, a la realidad actual donde
la versión X está en plena producción.
Decía entonces: “No niego que, en una primera instancia, me sorprendió el
tono informal del libro. Luego de leerlo cuidadosamente, veo que el “tono
informal” es un acierto más de los múltiples que han tenido los autores…”: dos
años después ratifico aquellas palabras.
Espero una muy buena acogida de esta segunda edición del libro que,
acompasándose con los tiempos y la tecnología, se puede obtener libremente en
www.genexus.com/genexus/libroepisodio1ed2 y, desde ya, felicito a los
autores por su trabajo y les deseo el mayor de los éxitos.
4
Estimado lector
Para el XVII Evento Internacional de Usuarios GeneXus, realizado en
setiembre de 2007, nos pidieron escribir un libro de ágil y amena lectura sobre
la versión de GeneXus que se encontraba en plena gestación: la entonces
denominada versión Rocha, hoy la X, una verdadera refundación del producto.
Desde ese entonces “la X” ha crecido mejorando funcionalidades e
incorporando algunas otras. Por esa razón, sentimos la necesidad de adaptar
aquella primera edición de nuestro libro “GeneXus Rocha – Episodio Uno”
para que siga cumpliendo con su objetivo inicial: mostrar GeneXus a quien,
nuevo en esta herramienta, tenga inquietud o deseos de descubrirla, así como a
quien, conocedor de versiones anteriores, quiera redescubrirla en esta nueva y
fundamental etapa que inicia.
He aquí entonces la segunda edición actualizada de aquel libro. Fue
elaborada con GeneXus X, upgrade 4. Actualmente, Artech se encuentra
trabajando para liberar en unos meses más GeneXus X Evolution 1, que incluye
nuevas e importantes funcionalidades, como el permitir generar, además de
aplicaciones Web, aplicaciones Win.
Esperamos que pueda ir siguiendo las páginas de este libro con el mismo
entusiasmo que nos llevó a escribirlas, y que pueda adaptar la lectura a sus
ganas y necesidades. Hay capítulos más técnicos que otros, en especial el 3 que
trata sobre Patterns. No pudimos ceder ante la tentación de mostrar toda su
potencia, lo que nos llevó a desnudar cada vez un poquito más. Quien quiera
mucho, lo leerá todo, quien no, lo leerá más superficialmente. Tómese esa
libertad, que fue la que nos permitió dar rienda suelta a nuestras ganas de
contar. Los últimos capítulos probablemente sean los más ágiles y disfrutables.
Para entender más cabalmente lo explicado, sugerimos ir realizando con la
versión Trial de GeneXus, de distribución gratuita (www.genexus.com/trial), la
aplicación que nuestros personajes irán desarrollando a lo largo del libro. Si
bien podrá elegir el generador y DBMS que prefiera, si es usted nuevo en
GeneXus, le recomendamos elegir los default que son lo que utilizarán nuestros
5
personajes en el libro. Encontrará en nuestro Download Center,
www.gxtechnical.com/genexus/libroepisodio1ed2/zip para ser descargado en
forma gratuita, un zip con el contenido final del proyecto desarrollado. Podrá
crearse otro proyecto (KB) e importarlo. Le servirá para comparar lo que va
haciendo, con la aplicación ya implementada.
Encontrará repetidamente en este texto citas a un whitepaper de Breogán
Gonda y Nicolás Jodal, “Desarrollo basado en conocimiento. Filosofía y
fundamentos teóricos de GeneXus”, publicado en mayo de 2007.
Por último, volvemos a agradecer a todos los compañeros que en
oportunidad de la primera edición estuvieron de un modo u otro aportando a
este trabajo. A todos: ¡gracias!
Mary, Diego, Mike y Julia, esperan ansiosos entrar en escena…
¡Buena lectura!
Los autores.
Montevideo, Mayo de 2009.
6
Sinopsis
Capítulo 1: El encuentro. Diego y Mary se reencuentran luego de años sin
verse, dando inicio a esta historia de encuentros…con GeneXus.
7
8
Capítulo 1
El encuentro
Alguien cuenta que esperando por fin ser atendido en un banco, escuchó
casualmente una conversación entre un hombre y una mujer, que también
esperaban. Según parecía, se reencontraban luego de años sin verse; ¿antiguos
compañeros de estudio?
- ¿Cómo estás Mary? ¿Cómo van tus cosas? ¿Sigues trabajando donde
siempre?–dijo el hombre con voz profunda, impostada, mirada fija, aire
seductor.
- Bueno Diego, luego de tantos años trabajando en la empresa, logré
ascender y ahora soy Developer Team Manager desde hace ya dos años – dijo
la mujer de rostro sensiblemente cansado.
- Supongo que debo felicitarte, pero ¿por qué esa cara? ¿tienes
problemas en el departamento? He escuchado que los sistemas de ustedes,
además de ser de gran porte, son fiables…
- Lo son, pero no sabes lo que cuesta su mantenimiento. Me estoy
enfermando, Diego, no duermo bien; muchas responsabilidades, grandes cargas
horarias, poco descanso. Siento en el pecho…
- …una presión – completó Diego – Entiendo, Mary – dijo al tiempo
que depositaba su mano derecha sobre el brazo izquierdo de ella-. ¿Cómo están
trabajando? ¿Qué herramientas están usando?
- Y… de todo un poco, lo usual… al final todo es lo mismo…– contestó
Mary ajena a esa cercanía- Tengo un equipo importante de personas trabajando
en los desarrollos y mantenimientos, pero aún así no damos abasto.
- Bueno, no todo es lo mismo… ¿Qué te parece si terminamos este
trámite aquí y vamos a tomar un café y me sigues contando? –preguntó Diego a
la vez contento y turbado.
9
¿GeneXus?
Ya en el bar, café mediante, Diego escuchaba atentamente a una Mary
angustiada. Recordaba su antigua firmeza, su empuje. Sentía un egoísta dejo de
alegría de poder ser ahora él quien la ayudara.
- ¿En cuántos proyectos están? – preguntó Diego para instaurar un
clima confesional.
- Ahora en uno solo. Pero es más que nada en tareas de mantenimiento.
Nuestros clientes nos están solicitando cosas nuevas todo el tiempo, pero nos
está siendo imposible satisfacer nuevos requerimientos. Además, esta tarea de
mantenimiento es realmente costosa, y ya no puedo justificar a la Dirección la
incorporación de más recursos humanos; nos saldríamos del punto de
equilibrio.
- ¿No será que los sistemas están un poco anticuados, tecnológicamente
hablando? ¿No les convendría hacer una reingeniería de todo a nuevo? –
preguntó Diego con liviandad.
- Sí, eso quisiera, y eso he propuesto. Pero los tiempos realmente no nos
dan, y como te dije, puede que estemos ya pasados de costos. No sé qué voy a
hacer… estoy agotada Diego, me siento en un callejón sin salida.
- Mary, no desesperes, ¿sabes cuántos están como tú? Tu caso es uno
más entre muchos. Las aplicaciones son cada vez más complejas, y con las
herramientas manuales de las que disponíamos, se está tornando imposible
desarrollar y peor aún, mantener esos sistemas, pues se están volviendo
inmanejables. Pero no todo es negro, Mary. Hoy en día hay soluciones. La
alternativa es delegar en procesos automáticos el trabajo que hacemos los
humanos, de manera que podamos dedicarnos a las nuevas complejidades y no
a tareas como la codificación que pueden ser efectuadas por un programa.
Nosotros, sin ir más lejos, estamos llevando adelante varios proyectos
importantes en simultáneo, y no tenemos demasiados inconvenientes. ¿Conoces
algo de GeneXus?
- Ah, GeneXus. Sí, algo he escuchado, pero no he tenido tiempo de
interiorizarme demasiado… ¿Ustedes trabajan con esa herramienta? He oído
10
que cada vez más gente no técnica puede usarla, lo que me despierta cierta
curiosidad.
- Mira Mary, no sé si GeneXus sea la panacea, pero sí sé que cura
varias de las dolencias que tienen empresas como la tuya. Es un programa que
hace programas. Automatiza la construcción y mantenimiento de tus
aplicaciones y de tu base de datos. Obviamente está en un nivel de abstracción
superior a otras herramientas, ¿comprendes? Contéstame a esta pregunta: si tú
pudieras disponer de un software que te permitiera lograr hacer esto
automáticamente, ¿lo usarías?
- Absolutamente.–.. Se incorporó en su silla–. Sí, justamente ése es
nuestro problema. Tenemos que hacer casi todo a mano…
La gran dificultad de la empresa de Mary, pensó Diego, era el de tantas
otras: imposibilidad de enfrentar nuevos proyectos porque los tiempos de
mantenimiento de los sistemas actuales, fundamentados en metodologías
tradicionales, lo impedían. Y eso, para una empresa que vive del desarrollo, es
una luz roja de peligro.
Bueno, no lo diga en voz alta, pero usted, estimado lector, se estará
preguntando: ¿Cómo?
- ¿Cómo? – preguntó ya más vital.
- ¿No te parece razonable pensar que dado un conjunto de visiones de
datos, - se interrumpió Diego con cierta malicia - exista un único modelo
relacional mínimo que lo satisfaga?
- Hmmm… puede ser, no estoy segura. ¿Pero qué tiene esto que ver con
mis problemas de mantenimiento?
- Y si eso es cierto –continuó Diego, ignorando la vacilación de su
compañera- ¿no te parece posible encontrar un procedimiento de ingeniería
inversa que partiendo de ese conjunto de visiones dé como resultado el
esquema de esa base de datos relacional mínima? Bueno, si me vas a decir “ver
para creer”, con gusto acepto una apuesta, como en los viejos tiempos; pero
desde ya te adelanto: vas a perder.
11
Una Apuesta
12
forma normalizada, y también utilizando el lenguaje de programación y base de
datos que se le indique. Así que te permite obtener un proyecto con el
conocimiento de la empresa…
- ¡Ah! Entonces eso…
- Sí –le interrumpió Diego completando la idea-, te permite ganar algo
muy importante: la portabilidad futura de la aplicación. Todo ese conocimiento
-reglas de negocio, diálogos, controles, listados- que hoy están en un lenguaje
de programación determinado, pueden ser convertidos a otros lenguajes sin
necesidad de arrancar de cero. O sea que se reutiliza el conocimiento porque la
Knowledge Base es conocimiento independiente de la plataforma.
Mary parpadeó rápidamente un par de veces y, sin mediar palabra alguna,
pidió otra ronda de café.
13
pone el énfasis en este modelo, porque es donde realmente está el conocimiento
genuino, es el realmente importante para los usuarios y los desarrolladores. En
él se recoge el conocimiento exterior y todo lo demás, como otros modelos
auxiliares que pudieran ayudar, puede inferirse automáticamente a partir de ese
Modelo Externo.
- Ah…el Modelo Externo…no puede contener ningún elemento físico o
interno como archivos, tablas, entidades, relaciones entre entidades, índices…
¿verdad?
- ¡Exacto! O cualquier otro que se pueda inferir automáticamente –
exclamó Diego animado, recuperando el entusiasmo que siempre le provocaron
las conversaciones con Mary– El Modelo Externo será utilizado para obtener y
almacenar el conocimiento. Pretende la representación más directa y objetiva
posible de la realidad.
- Claro, y con eso te independizas de la implementación, porque tienes
una descripción del sistema en alto nivel, descripción que sólo cambiará si
cambian las visiones sobre el mismo; ¿voy bien?
- ¡Perfecto! Por
ello tomamos las visiones
de los diferentes
usuarios… Visiones que
son almacenadas en el
modelo. Luego se captura
todo el conocimiento
contenido en ellas y se lo
sistematiza para maximizar las capacidades de inferencia…
- Eso es lo que no termino de creer. Me suena a pura teoría… ¿tú
intentas decirme que es posible sólo describir y que un programa, con sólo un
par de pases de varita mágica te haga realidad la aplicación? – preguntó Mary
intentando ser convencida por los razonamientos apasionados de Diego.
- Bueno, yo no diría que es magia. Es un software al fin de cuentas. Y
no digo que no tengas que programar algo, pero sí digo que “casi” nada, lo
14
menos posible. En GeneXus vas a tener algún código que será procedural, pero
el lenguaje en que lo escribes, no es el lenguaje al que estás acostumbrada… en
él tampoco tienes que nombrar tablas, ni índices, ni nada de lo físico de una
base de datos.
- Pero… no entiendo entonces dónde entra el Modelo Relacional –
preguntó Mary confundida.
- Es el que se utiliza para representar y manipular los datos: es el
modelo interno o físico, pero no será creado por ti, será inferido por GeneXus
con su procedimiento de ingeniería inversa. Mi apuesta fue tramposa.
- ¿Y cómo describes las visiones de los usuarios, el Modelo Externo, de
manera que pueda aplicarse ingeniería inversa sobre ellas y obtener todo eso
que dices? Porque tiene que ser lo bastante formal y rigurosa esa descripción
como para que no haya ambigüedades y que un programa pueda “inferir” todo
eso que los programadores hacemos todavía a mano.
- Sí, lo es. Las descripciones se realizan a través de determinados
“objetos-tipo” de GeneXus que indican “el qué”. A través de ellos, GeneXus
encuentra el “cómo”. Por lo que, para aprender a usar GeneXus, tienes que
aprender a describir, a usar esos objetos-tipo. No necesitas grandes
conocimientos técnicos; cada vez menos.
Diego continuaba hablando, llenando el reducido silencio que su
interlocutora había dejado. A pesar de tantos años sin verse, conocía bien a
Mary, conocía esa expresión de ojos pensativos, y sabía que su silencio lo
invitaba a proseguir. No podía tener mejor suerte - ¡Bendito GeneXus! - pensó,
y continuó en tono confidente, manejando las pausas, seguro de lograr los
efectos buscados.
15
- Para lograr todo esto,
GeneXus tiene una base de
conocimiento, que
inicialmente tiene asociado un
conjunto de mecanismos de
inferencia y algunas reglas de
aplicación general, como las
que aseguran la consistencia
(las de integridad referencial, por ejemplo). Luego, cuando el analista GeneXus
comienza a describir la realidad creando objetos, estas descripciones (el
Modelo Externo) son sistematizadas automáticamente y pasan a estar
contenidas en la base de conocimiento… Además, sobre ese conocimiento,
obtiene un conjunto de resultados que le ayudan a mejorar la eficiencia de las
inferencias posteriores.
- ¡Es una máquina de inferencias! – exclamó Mary.
- Nada más cierto. Por ejemplo, dada una visión de los datos, puede
inferir automáticamente el programa necesario para manipularla.
16
- Diego, debo irme, pero quiero saber más. Me has dejado con la cabeza
confusa. Es muy grande el cambio de mentalidad, quisiera palpar esto más de
cerca, verlo funcionando. ¿Te parece de encontrarnos la semana próxima,
misma hora, mismo lugar?
- Ciertamente – expresó Diego en tono pretendidamente sereno.
17
18
Capítulo 2
Érase una vez… un Proyecto
Travel Agency & Co. es una novel compañía de venta de tickets de viajes y
turismo que ha contratado a la software house ACME Business Software
Solutions a fin de que le sea desarrollado un sitio en Internet que permita a sus
clientes realizar búsquedas de destinos, de vuelos existentes, de servicios
brindados, reservar tickets y obtener pasajes y servicios turísticos (front-end).
Incluirá también todo el sistema para el mantenimiento de la información
relacionada (back-end).
ACME, que viene trabajando con GeneXus en sus sucesivas versiones
desde hace un tiempo, está integrada por varios técnicos; entre todos ellos,
Julia, Mike y Diego fueron los seleccionados para llevar adelante este
desarrollo. Diego y Mike son Analistas GeneXus y los que normalmente se
encargan de la implementación que les toque en suerte. En particular, Mike se
encarga casi siempre de la tarea de testear las aplicaciones, mientras que Diego
se encarga del desarrollo. Julia, en cambio, no es técnica en GeneXus, si bien
ha trabajado en muchos proyectos relevando requerimientos; su especialidad es
documentar y moderar las charlas entre los integrantes del equipo a medida que
se van produciendo los avances. El hecho es que los tres conforman un equipo
donde todos tienen voz y voto.
Las primeras tareas recaen sobre los hombros de Julia y Diego mientras
Mike realiza los testings de las otras aplicaciones de ACME en curso para los
desarrollos en otros clientes.
Desde siempre, Julia documentaba en procesadores de texto, y en proyectos
de envergadura le resultaba extremadamente engorroso el mantener decenas de
carpetas con cientos de archivos, conjugarlos entre los desarrolladores,
coordinar las discusiones del equipo (cuando lograba hacerlo), entre otras
tareas. Usted comprenderá el impacto que sintió luego de enterarse que
GeneXus X contenía, en forma embebida, un perfil de documentación muy
particular que facilitaría su vida de ahora en más.
19
El tiempo es tirano
Un breve tiempo atrás, los directores y el equipo de desarrollo se pusieron
de acuerdo en el objetivo de un proyecto a corto plazo, tres semanas o algo
así. El hecho de que iban a trabajar con GeneXus X, a sabiendas del gran
aumento de usabilidad y productividad que ésta traía incorporado, les había
permitido reducir a la mitad los plazos que normalmente pactaban con sus
clientes.
“Cada usuario tiene una o múltiples visiones de los datos que utiliza
cotidianamente. Entre estas visiones, podemos pensar en un primer tipo: el
que agrupa aquellas que se utilizan para manipular los datos (introducirlos,
modificarlos, eliminarlos y visualizarlos en forma limitada), a estas visiones de
usuarios les hemos llamado Transacciones y constituyen el primer objeto-tipo
de GeneXus”.
Breogán Gonda & Nicolás Jodal
20
variados en el mundo. ¿Qué les decía esto? Antes que nada, que si los
destinos eran puntos turísticos de ciudades distribuidas a lo largo y ancho del
mundo, debían disponer de un almacenamiento para ello.
Es así que el equipo completo se reunió y luego de intercambiar notas e
impresiones, concordaron en que Julia comenzara a describir las primeras
pautas obtenidas, mientras el resto, en especial Diego, comenzaría a
representar en GeneXus aquellas visiones de los usuarios más claramente
descriptas.
21
Panels
22
ofrece los nombres de las últimas bases de conocimiento que han sido abiertas,
con sus fechas de última modificación; note que también están presentes links
que permiten abrir o crear una nueva base de conocimiento.
Debajo, un contenedor Extensions presenta herramientas desarrolladas por
Artech o terceros, que pueden ser instaladas y utilizadas libremente, y permiten
extender en todas direcciones la potencia de la herramienta.
El sector rotulado GeneXus Community, contiene sindicaciones de
contenidos de sitios Web vinculados a la Comunidad que se actualizan con
frecuencia (RSS). Note que debajo de cada noticia aparece More… como link al
documento propuesto.
A su vez observe la celda Address que le permitirá navegar por la Web o
por su sistema de archivos sin salir del IDE.
23
en Java tendrás que indicar cosas tales como dónde estarán las clases en la
webapp, algo que en .Net no tiene sentido. Es decir, en base al ambiente que
hayas elegido, te muestra propiedades necesarias para configurarlo, para que
GeneXus pueda luego decirte: “sus deseos son órdenes” y te implemente la
aplicación como se lo pediste.
Casualmente, mientras Diego y Mary intercambiaban palabras y miradas en
el café, Julia realizaba esos mismos pasos que lo invitamos a seguir a usted
también.
TravelAgency
24
Observe además que la KB será almacenada en una base de datos que por
defecto recibirá el nombre GX_KB_nombre en la instalación del
SQLServer local que usted tenga. Si no tiene una local, podrá utilizar una
de la red. Presionando el botón [Advanced] podrá modificar estos valores.
25
Sobre esta base de conocimiento trabajarán los demás integrantes del
equipo; Julia está ahora lista para comenzar a documentar.
Files
Julia había recibido por e-mail varios archivos de documento de los jefes de
sección de cada departamento con requerimientos para el sistema, donde
listaban las prioridades y características de sus sectores, algunos flujos de
procesos, etc.
26
Como estos documentos ya estaban hechos por terceros, decidió que sería
importante incorporarlos en su estado original dentro de la base de
conocimiento, de forma tal que siempre estuvieran disponibles para su consulta.
Eran importantes las “palabras” de los usuarios expuestas por ellos mismos.
Así, expandió el nodo Documentation del árbol y seleccionó Files
abriéndolo con doble clic. Como resultado, un nuevo tab apareció dentro de la
ventana principal, rotulado Files. Allí presionó Add New File y se le abrió una
ventana que le permitió explorar su sistema de archivos, y elegir cada archivo a
ser insertado en la KB. Así quedó el tab Files luego de esto.
Images
27
Main Document
Note que le ha dado algún formato al texto. Para ello se valió de la toolbar
formatting:
28
Julia ha cambiado el tipo de letra, su tamaño, e incluso
puso en negrita el tópico sobre el cual se comenta.
Pero además, y bien importante para lograr una
documentación integrada, Julia necesitaba agregar links
hacia los documentos recibidos e insertados en la KB
instantes antes. Para ello se valió de la toolbox1, seleccionó
Table de la misma, arrastrándola a la página y luego de
insertada la tabla, en cada fila escribió:
1
El IDE de GeneXus ofrece varios paneles con utilidades que se pueden acoplar en cualquier parte de la ventana y también se
pueden ocultar automáticamente cuando pierden el foco para que no ocupen sitio en la ventana principal. Usted puede activarlas
mediante View/Other Tool Windows.
29
Observe que como consecuencia de los símbolos que empleó en la segunda
columna de la tabla, los documentos se encuentran subrayados: son links a los
archivos almacenados. Esto aporta un gran dinamismo a la documentación, y la
integra en forma activa en el proyecto a través de la KB.
El editor de documentación de GeneXus utiliza dos paréntesis rectos de
apertura ([[) y dos de cierre (]]) en el texto para crear links.
Si usted está siguiendo el ejemplo en forma práctica, habrá notado que,
apenas pulsado el segundo paréntesis de apertura, el editor le propuso una lista
de objetos válidos para seleccionar, tal como mostramos en esta imagen:
30
Julia seleccionó File y al digitar ‘.’ se abrió la lista de los archivos
disponibles (también podría haber arrastrado desde la toolbox el objeto File y
habría obtenido igual resultado que al escribir “[[File.”).
31
“Centenario Stadium” pertenece a la categoría “Great Monuments” o algo así, y
“Disney World” a “Entertainment”. Estas son sus visiones de los datos…
- Ajá. ¿Y cómo describes esas visiones objetivamente?
- Mediante atributos… y objetos de tipo Transacción. Déjame explicarte
un poco. Los atributos son el marco de referencia sobre el cuál hacer las demás
descripciones. No es exactamente lo mismo que un atributo en un modelo
relacional. El atributo es el elemento semántico fundamental. El significado del
mismo vendrá dado por su nombre, por lo que los nombres de los atributos
pasan a ser esenciales en GeneXus.
Diego tomó una servilleta de papel de la mesa del bar y garabateó algunos
nombres: AttractionName, CountryName, CityName, AttractionImage,
AttractionCategoryDescription. Y prosiguió:
- Estos atributos tienen un contenido semántico claro, que cualquier
persona podrá entender sin necesidad de contexto alguno. No son sólo “Name”
o “Description”. Existe una fuerte relación entre el nombre del atributo y su
significado. Ese nombre, a su vez, es una secuencia de caracteres, algo
sintáctico, único, con lo que cualquier programa puede trabajar, sin
ambigüedad. Si yo coloco AttractionCategoryDescription en algún lado, tú y
yo sabemos que estamos hablando de la descripción de la categoría de la
atracción. GeneXus lo reconoce, por su sintaxis.
- Ajá, entonces –interrumpió Mary– claramente no puedes usar el
mismo nombre de atributo cuando quieras referirte a otro dato, pues ahí sí
habría confusión y ambigüedad.
- Exacto. Un atributo deberá tener el mismo nombre en todos los
lugares donde aparezca y no podrá haber dos atributos diferentes, con
significado diferente, que compartan el mismo nombre. En eso se basa. Hay
una excepción, pero no te quiero confundir ahora. Estas visiones que me
expresaron los usuarios, corresponden a aquellas que se utilizan para manipular
los datos (ingresarlos, modificarlos, eliminarlos, visualizarlos), y son las que se
representan en GeneXus mediante el objeto de tipo Transacción. Cada
transacción tiene un conjunto de elementos: una estructura, reglas, fórmulas,
32
elementos de presentación. Es decir, en un mismo objeto, se matan varios
pájaros de un tiro: al tiempo que dices con qué información se va a trabajar,
también diseñas la pantalla que se desplegará al usuario final para hacerlo,
declaras el comportamiento que deberán tener esos datos cuando se vayan
ingresando, y demás.
Diego volvió a tomar la servilleta que había dejado sobre la mesa, y esta vez
escribió (y luego dijo):
-
-
33
- Sí, indirectamente. Y no: yo no me preocupé de “diseñar” una tabla
física del modelo relacional. Solamente me limité a especificar los atributos con
los que el usuario final interactuará al ingresar atracciones al sistema.
Especifiqué una vista de usuario, no una tabla. Aquí entra lo de la ingeniería
inversa y el Modelo Relacional “inferido” por GeneXus. Si no hubiesen más
transacciones que ésta en la base de conocimiento, entonces GeneXus
“inferiría” una tabla física compuesta exactamente por los atributos listados.
Pero la cosa cambia si existen otras transacciones compartiendo algunos de los
atributos mencionados. Allí habrá que “normalizar”. Pero no me quiero apurar,
espera un poco que ya te contaré de esa parte.
- Creo que te voy entendiendo, Diego. Pero me llama la atención que
pusiste en la estructura de la transacción atributos que no dijiste que hubieran
mencionado los usuarios. Todos los que terminan con Id… Por otro lado, ¿por
qué no utilizaste el nombre de la atracción como identificador?
- ¿Y si el nombre de la atracción pudiera repetirse para atracciones de
distintos países y ciudades? Suponte: “Disney World” que existe en París,
Miami, Las Vegas, Orlando. En este caso, necesitamos un atributo que dé
unicidad al conjunto de elementos de información que constituyen una
atracción. Toda estructura de transacción debe tener un atributo o atributos que
identifiquen a cada instancia. En principio, en esta transacción, coincidirá
exactamente con la clave primaria de la tabla “inferida”. Pero no siempre será
así, como verás cuando te muestre un ejemplo de transacción con niveles. No te
preocupes ahora. En nuestro caso, podremos decirle a GeneXus que
AttractionId será numérico y que queremos que se numere automáticamente.
- ¿Y qué me dices de los atributos AttractionCategoryId, CountryId,
CityId? ¿Más identificadores?
- Sí, serán identificadores en otras transacciones. Me explico:
evidentemente los países y ciudades, así como las categorías de atracciones,
corresponden en sí mismas a entidades independientes de la atracción turística.
Por ejemplo, no hemos entrado a ver eso aún con este cliente, pero
evidentemente los vuelos aéreos también tendrán país y ciudad (de origen y de
34
destino). Es decir, resulta evidente que deberemos crear una transacción para
representar e ingresar la información de países y sus ciudades, así como de las
categorías de atracciones existentes. Esos identificadores de los que me
preguntabas, serán, casualmente, los que identifiquen cada dato (instancia) de
estos.
- A ver si entiendo: una vez que definas esas otras transacciones,
GeneXus inferirá tablas para almacenar los datos que manipulan, y a partir de
allí, se dará cuenta que esos atributos que colocaste en la estructura de la
transacción Attraction, (AttractionCategoryId, CountryId y CityId) son los que
se traducirían en claves foráneas en la tabla relacional asociada.
- ¡Sigues siendo la misma luz de siempre, Mary! – exclamó Diego con
ojos iluminados.
- Pero entonces, ¿qué hacen en esa estructura CountryName, CityName,
AttractionCategoryDescription? En un modelo relacional normalizado nunca
podrían estar en esa tabla.
- Es que no lo estarán. Por eso te decía que la estructura de una
transacción no se corresponde exactamente con la de la tabla física asociada.
Ya te lo explicaré, no quiero abrumarte con detalles. Traeré mi notebook al
próximo encuentro… porque lo habrá, ¿no? –inquirió Diego vacilante, y
prosiguió sin esperar respuesta–, y te mostraré…
Ahora el lector verá los pasos que siguió Diego en la creación de la
transacción mediante el IDE de GeneXus al día siguiente en su trabajo.
Primero que nada, seleccionó el link New Object de la Start Page y
apareció a su vista la ventana cuyo diálogo permite la creación de un objeto
dentro de la KB. De la caja Select a Type, con la lista de objetos GeneXus
disponibles, seleccionó Transaction y luego, en la celda Name escribió el
nombre “Attraction”, quedando una imagen semejante a la que se muestra a
continuación.
35
A continuación, pulsó [Create], tras lo cual la transacción se abrió.
Los objetos que se abren en la ventana principal del IDE aparecen como tabs,
donde uno solo, el activo, se presenta en la ventana central. El objeto abierto
puede tener varios elementos o partes, en cuyo caso será uno el activo en cada
oportunidad. En el tope inferior de la ventana aparece una barra de acceso que
permite, mediante Selectores, seleccionar otra de las partes del objeto activo.
36
Selectors
37
El tipo de datos Blob posibilita el almacenar una gran diversidad de tipos de
información (videos, planillas, documentos de todo tipo, archivos de música,
imágenes digitalizadas, etc.) en la propia base de datos. La idea del atributo
AttractionImage es justamente almacenar una imagen de la atracción, que
podrá ser un archivo bmp, jpg, etc.
La columna Description pretende que se asigne una descripción ampliada
del atributo. Por ahora dejaremos las sugerencias de GeneXus a partir del
nombre del atributo.
Para continuar agregando atributos, simplemente pulse <Enter> luego de
terminada una definición y repita el mecanismo.
Atributos Clave. Como puede usted apreciar, el atributo clave que Diego
indicaba con un asterisco en la servilleta, se indica en GeneXus por el ícono de
una llave.
38
Cada dominio es un conjunto de características únicas que pueden ser
compartidas por varios atributos. Esto proporciona consistencia y facilita el
mantenimiento, ya que cambiando alguna de las características de un
dominio se provocará la propagación del cambio a todos los atributos
basados en él.
Definición de Dominios
Diego hizo doble clic sobre el nodo Domains en el árbol del Folder View
tras lo cual se abrió en la ventana principal el tab que muestra y permite editar
dominios. Ingresó así los dominios Id y Name. El mecanismo es semejante al
de crear atributos en una transacción.
2
En general en GeneXus existen varios caminos que le permiten realizar lo mismo, para que el efectuar su tarea le resulte
siempre cómodo.
39
Observe que el asterisco que seguía al nombre de la transacción en el tab
desapareció (el asterisco indica que los cambios que se han realizado aún no se
han grabado) y que el selector “Structure” cambió de color.
La Tabla ATTRACTION
Por defecto, cuando GeneXus diseña las tablas les asigna como nombre el
mismo que el de la transacción en la que están basadas3. En todo momento
puede verse el diseño de la tabla (su estructura, índices, etc.) abriendo el nodo
Tables del árbol que se encuentra en el panel Folder View y haciendo doble clic
sobre el nombre de la tabla. Para ATTRACTION se verá:
3
En lo que sigue en este material, para diferenciar los nombres de las tablas de los de las transacciones usted verá a las tablas
escritas en mayúsculas.
40
Dada la estructura de la transacción, GeneXus diseñará también un form
para la misma, que será la pantalla a través de la cuál el usuario ingresará
atracciones cuando la transacción sea convertida en programa ejecutable. Puede
verse escogiendo el selector WebForm4 de la barra de acceso:
4
Con GeneXus X usted podrá generar exclusivamente aplicaciones Web 2.0. Para generar una aplicación Win, podrá utilizar
GeneXus X Evolution 1. Allí la transacción presentará también un WinForm.
41
Este form podrá ser personalizado, moviendo de lugar, insertando,
modificando, eliminando controles, cambiando su aspecto, etc.
Conforme con el resultado, Diego recordó que no había documentado nada
sobre la transacción. Es norma de los integrantes de ACME el ir documentando
a medida que se producen los avances, sobre todo en lo que respecta a las
entidades, tal como la transacción Attraction.
Así que abrió Main Document, insertó una nueva tabla, esta vez titulada
“Objects” y agregó en la primera fila un link hacia la transacción recién creada
(del mismo modo en que lo hizo Julia varias páginas atrás, al incluir los links a
los documentos), junto con una breve descripción.
42
Podemos ver Attraction como link. ¿Qué sucederá cuando Diego haga clic
sobre el mismo? Se abrirá un nuevo tab de documentación, esta vez de la
transacción, donde podrá editarla, e incluso abrir la propia transacción:
43
hacerlo, y luego para efectivamente ir a la ventana de edición de la
documentación, podrá directamente elegir el selector Documentation. Como
podrá constatar usted mismo, estimado lector, en GeneXus también se aplica
aquello de que “varios caminos conducen a Roma”.
44
Con Main Document abierto en edición, agregó un nuevo link en la tabla,
ahora a la transacción Country. Pero ¡todavía no la había creado! Observe que
aparece un link, sí, pero sobre un símbolo ?:
45
Cuando Diego haga clic sobre ? automáticamente se creará la transacción
que aparecerá abierta en la estructura, lista para que él comience a ingresar los
atributos, y el link quedará ahora definido en Main Document. (Sin ánimo de
ser reiterativos: ¿cómo era aquello de los caminos a Roma?).
46
Como CountryId y CityId ya existen en la KB (recuerde que fueron creados
al grabar la transacción Attraction) al comenzar a digitar las primeras letras de
cada uno, GeneXus le abrirá una lista conteniendo los atributos de la KB que
empiezan igual, de modo tal que usted pueda seleccionar el que corresponda y
evitar seguir digitando el nombre completo. Ídem con CityId y CityName.
también es interesante destacar que cuando inserte el atributo CountryDetails, GeneXus ya inferirá como su tipo de datos, el
dominio Details (ya que lo contiene como última parte de su nombre).
47
Recuerde que una dependencia funcional XÆY es una restricción entre dos
conjuntos de atributos de la base de datos, que establece que si para 2 registros
coinciden los valores de X, entonces también tienen que coincidir los valores
de Y. Esto significa que los valores de los atributos en Y en cualquier registro,
dependen de los valores de los atributos en X, o están determinados por ellos.
1. Todo país se identificará (de manera única) por el valor de CountryId y que
este valor tendrá asociado (determinará) un CountryName (y sólo uno),
CountryFullName, etc. (establece esas dependencias funcionales). En un
Modelo Relacional normalizado, esto deriva en una tabla física con clave
primaria CountryId para almacenar esta información respetando esta
unicidad establecida.
48
2. Toda ciudad se identificará para un país dado (CountryId), por el valor de
CityId, y que por tanto, este par (CountryId, CityId) determinará un
CityName y CityInformation. De esto se desprende que deberá existir una
tabla física con clave primaria {CountryId, CityId} para almacenar la
información de cada ciudad6.
Pero entonces: ¿qué hace GeneXus con los atributos de igual nombre en
diferentes transacciones? Teniendo presentes los dos enunciados mandatarios
en la filosofía GeneXus:
49
pantalla cuyo diseño será el del WebForm, podrá, por ejemplo, ingresar una
nueva atracción (nuevo registro en la tabla ATTRACTION subyacente). Sería
deseable que cuando el usuario final colocara un identificador de país en el
campo correspondiente a CountryId, y uno de ciudad en CityId pudiera
visualizar automáticamente el nombre de dicho país, CountryName y de dicha
ciudad, CityName. Es decir, que CountryName y CityName sean traídos en
ejecución de las tablas COUNTRY y COUNTRYCITY a través de la clave
foránea {CountryId, CityId}. Para poder colocar en el form atributos a los que
se llega a través de claves foráneas, es necesario que estén presentes, inferidos,
en la estructura.
¿Qué sucedería si el usuario digitara para CountryId, CityId un par de
valores inexistentes? ¿Y si mediante la transacción Country se quisiera eliminar
una ciudad para la que existieran atracciones? Si se nos permitieran hacer estas
operaciones, se estaría violando una de las condiciones más importantes de los
modelos relacionales para mantener la consistencia de los datos: la integridad
referencial. De hecho GeneXus agregará automáticamente en las transacciones
el código necesario para realizar estos controles sin que el desarrollador se
moleste en hacerlo.
Integridad Referencial
Diego comunicó a Julia que ya había definido las dos primeras
transacciones. A los efectos de comprobar que el modelo reflejara la realidad,
ella hizo un Diagrama de Bachman (diagrama de relaciones entre tablas) sobre
las tablas inferidas por GeneXus a partir de las transacciones.
Para ello expandió el nodo Tables del contenedor Folder View, luego,
posicionada sobre cualquier tabla, pulsó el botón derecho para abrir el menú
contextual y escogió New/Object, tras lo cual se abrió la ventana Create New
Object donde seleccionó el objeto Diagram para finalmente pulsar [Create].
Seleccionó las tres tablas simultáneamente y las arrastró hacia la hoja en
blanco, obteniendo el siguiente resultado.
50
En este tipo de diagrama, la punta simple de la flecha representa la
existencia de una instancia de la tabla apuntada para cada instancia de la otra;
es decir: para cada ciudad existe sólo un país; para cada atracción turística
existe una sola ciudad. Recíprocamente, la punta doble representa la ocurrencia
de varias instancias de la tabla apuntada para cada instancia de la otra, es decir:
para cada país existen muchas ciudades relacionadas; para cada ciudad muchas
atracciones.
Esto permite determinar las relaciones entre ellas. Por ejemplo, la relación
entre COUNTRY y COUNTRYCITY es de 1 a N (1 a muchos) y viceversa es
N a 1 (muchos a 1). La de COUNTRYCITY con ATTRACTION también es 1
a N.
Para asegurar la integridad referencial, deberá entonces controlarse que
cuando se inserte o modifique un registro en la tabla ATTRACTION, exista el
registro relacionado en la tabla COUNTRYCITY. Y cuando se elimine un
registro de la tabla COUNTRYCITY, no existan registros en la tabla
ATTRACTION relacionados. GeneXus resuelve esto automáticamente,
agregando esta lógica a las transacciones Attraction y Country, en forma
transparente para el desarrollador. Análogas consideraciones pueden hacerse
51
respecto a los controles sobre las tablas relacionadas COUNTRY y
COUNTRYCITY.
52
concepto de tabla extendida. Una tabla que físicamente no existe, pero que te la
imaginas.
- Absolutamente cierto. Verás que utilizamos el concepto a lo largo y
ancho de GeneXus y por eso se le ha puesto un nombre.
Mientras Diego permanecía absorto en sus recuerdos, Julia comprobó que
se cumplían correctamente las necesidades de acceso a la información entre las
tablas.
Comenzando a Prototipar
Para poder dejarle paso a Mike, y que éste pueda comenzar a jugar su papel
de tester del proyecto, Diego pulsó <F5> para dar comienzo a la prototipación.
7
Si desea modificar alguna de ellas, alcanza con seleccionar el panel Preferences del contenedor Knowledge Base Navigator y
allí buscar la preferencia a configurar, por ejemplo el DBMS asociado, o el lenguaje de implementación, etc. y con <F4> sus
propiedades (en la ventana que se abre).
53
Su DBMS es Microsoft SQL Server 2005 Express y se encuentra instalado
en el servidor DevelopeSrv de la red. El nombre que Diego dará a la base de
datos es TravelAgencyTest:
8
Un sencillo menú de prototipación creado por GeneXus (es un xml conteniendo links a los distintos objetos GeneXus creados,
para poder invocarlos en forma fácil y rápida como se verá en breve).
54
El Impact Analysis Report (IAR) que estaba observando le decía que se
iban a crear las tablas que se mostraban a la izquierda, y cuyas estructuras y
demás información como índices, etc. 9, podían verse a la derecha.
¿Qué es el Impact Analysis Report? GeneXus diseña a partir de la base de
conocimiento la base de datos. En todo momento pueden hacerse
modificaciones a los objetos existentes, o pueden crearse nuevos objetos,
modificándose la KB. El IAR es el resultado de un análisis que realiza
GeneXus del impacto causado por las definiciones nuevas o cambios del
modelo de desarrollo en una KB sobre la base de datos física asociada. Por
tanto, este análisis tiene por finalidad informar al desarrollador sobre los
9
Obsérvese que abajo se muestran las sentencias SQL que se utilizarán para reorganizar (en este caso crear) esa tabla.
55
cambios estructurales que GeneXus deberá llevar a cabo sobre la base de datos
para dejarla en el nuevo estado, consistente con la KB.
De conformidad con el IAR, Diego pulsó [Create] dando paso a la
reorganización de la base de datos10. Como en este primer paso ésta no existía,
se va a crear. Será en sucesivos cambios que será reorganizada, en tanto
reestructurada. Ahora bien, ¿cuándo se generan los programas?
¡Inmediatamente, listos para ser probados! Desde que Diego presionó <F5> e
ingresó la información que le faltaba a la plataforma (nombre de BD y
servidor), hasta que se le abrió una ventana del Browser con el Developer
Menu listo para ejecutar, no pasó más de un minuto. Diego estudió
detenidamente unos listados desplegados por GeneXus antes, que le
permitieron ver que iba por buen camino… pero no se apure, estimado lector,
que sobre este punto volveremos más adelante y Mike está un poco ansioso por
entrar en escena.
10
El término reorganizar refiere a efectuar cambios físicos.
56
La idea era comenzar cuanto antes a probar, cargando información en la
base de datos recién creada por GeneXus y verificar el comportamiento de las
transacciones.
Pulsó sobre Country y apareció ante su vista una imagen semejante a la que
se observa a continuación.
57
Inmediatamente se percató de que el atributo CountryId debería ser
autonumerado, es decir, que el usuario no debería preocuparse por asignarle un
valor específico ya que CountryId no representa nada de la realidad, es sólo un
número que otorga unicidad al registro a ser insertado.
Asimismo, cuando Mike se enfrentó a CountryWorldRegion, atributo
numérico de apenas un dígito, no supo qué valor numérico representaba cada
región del mundo (¿cómo indicar que se trata de un país africano,
norteamericano, europeo, etc.?) “¡Hay que mostrarle al usuario final los
nombres de las regiones y continentes para que escoja el apropiado!”, se dijo.
Así que decidió iniciar con Diego una conversación sobre el tema, de forma
tal que todos estuvieran de acuerdo sobre lo que proponía. Volvió a la KB,
seleccionó Country y luego ingresó a su sección de documentación.
Agregando conversación
Entre los selectores que se presentan al abrir la documentación, aparece
uno: Talk, que tiene por finalidad permitirnos crear un espacio donde establecer
un diálogo con otros usuarios sobre el objeto en cuestión.
58
a escribir, ésta debe ser creada. Cuando Mike salve este espacio de memoria, se
generará una página con su contenido.
Al cliquear sobre el signo de interrogación, cambió al estado de edición e
ingresó el texto, grabó, y pudo ver:
59
Por lo que Diego se posicionó sobre el atributo CountryId y en la estructura
de la transacción Country pulsó <F4>11.
11
Sin necesidad de entrar a la transacción, también puede buscarse el atributo para editar sus propiedades con <F4>, a través de
la ventana Attributes, que se accede mediante View/Work With Attributes. Las propiedades que se modifiquen de cualquiera de
las dos maneras aplican al atributo en general, no al atributo en una determinada transacción en particular.
60
Cirugía estética para un atributo
Para completar el siguiente pedido de Mike en el Talk, Diego se situó en el
WebForm de la transacción, se posicionó sobre el control atributo
CountryWorldRegion y pulsó <F4> (Diego tiene la ventana de propiedades con
Auto Hide, así que ahora la ventana Properties se desplazó hacia afuera). Ya en
la ventana de propiedades del control, en la propiedad ControlType seleccionó
“Combo Box”; a continuación, seleccionó la propiedad Values para abrir la
ventana Values Editor y cargó los valores para el atributo. Eso fue todo.
61
Si en lugar de haber hecho esto específicamente en las propiedades del
control CountryWorldRegion del form lo hubiese hecho en las propiedades del
atributo CountryWorldRegion en la estructura, habría hincado el bisturí más
a fondo: significaría que en todos los forms donde se colocase ese atributo, por
defecto se mostrase como Combo Box, con la definición indicada y no como
control Edit. Cuando se percata de su distracción, Diego decide hacer esa
definición a nivel de la estructura. Aquí queda claro cómo es que el
conocimiento se infiere en la base de conocimiento, y es reutilizado.
Pero allí reconoce que lo más conveniente, sin embargo, será crear un
dominio enumerado para ser utilizado por cualquier atributo que quiera
representar regiones del mundo, en particular, CountryWorldRegion. Edita
entonces los dominios y crea uno nuevo, WorldRegion de tipo Numeric(1.0), y
en la ventana de propiedades (<F4>) se posiciona en la propiedad Enum values
y pulsando sobre el botón abre la ventana para ingresar los valores que
tomará el dominio enumerado:
62
De esta manera evitará en la aplicación tener que recordar que el valor 8
correspondía en su sistema a South America, y a partir de allí, cuando se quiera
referir a ese valor, escribirá WorldRegion.SouthA, que será sin duda más fácil
de recordar que 8.
Por tanto edita las propiedades del atributo CountryWorldRegion (por
ejemplo, posicionándose en ese atributo en la estructura de Country y pulsando
<F4>) y modificará su tipo de datos para que ahora sea del dominio
WorldRegion.
63
Observará inmediatamente que el valor de la propiedad ControlType del
atributo automáticamente pasa a ser “Combo box”, y sus valores los del
enumerado.
Reorganizando (reorg)
Diego cambió propiedades de dos atributos de la transacción Country, ¿esto
indirectamente disparará algún cambio en la tabla física? Veamos el siguiente
Impact Analysis Report que se observa luego de que pulsara <F5>:
64
Sí, GeneXus encontró que debe realizar una reorganización de la tabla.
Note que ahora aparece el texto “Autonumber” a la derecha del tipo de datos en
el atributo CountryId. La reorganización deberá “encender” esa propiedad a
nivel del DBMS. Pero, ¿qué pasó con el otro cambio, con el de
CountryWorldRegion? ¡Nada! No se trata de un cambio a efectuar sobre la base
de datos, sino que solamente vale a nivel de la KB: el dominio WorldRegion
sigue siendo de tipo Numeric(1.0) por lo que el atributo CountryWorldRegion
no cambia su tipo a nivel de la base de datos. El cambio principal será estético:
en todos los forms donde se presente ese atributo como control, lo hará como
Combo Box con los valores del dominio enumerado.
Aquí se evidencia que no todos los cambios que se efectúen en un objeto
GeneXus necesariamente provocarán una reorganización. Veremos otro
ejemplo de ello a continuación cuando hablemos de las reglas de una
transacción.
65
Diego puede decidir pedirle a GeneXus que haga efectivamente los cambios
informados ([Reorganize]) o cancelar la reorganización, con lo cual la próxima
vez que se presione <F5> volverá a realizarse el IAR. Elige reorganizar.
Al releer lo charlado en el Talk, con intenciones de contestar que todo fue
hecho, encuentra que Mike proponía que por defecto todos los Ids de
transacciones fueran autonumerados. (Su confusión vino a raíz de que Mike, en
lugar de haber documentado esto en el Talk del Main Document, lo hizo en el
de la transacción Country). Pero entonces, para implementar lo sugerido,
¿tendría que ir una por una a cambiar la propiedad Autonumber de esos
atributos? ¡No!, pronto recordó que todos ellos estaban basados en el dominio
Id. Alcanzaba con editar el dominio, sus propiedades y allí, de igual manera
que se hizo para el atributo CountryId, “encender” la propiedad Autonumber a
nivel del dominio. ¡Todos los atributos basados en el presente y en el futuro en
ese dominio, automáticamente heredarán la propiedad!
Pero…“¡#@!#!@...!”, maldijo en voz alta. “CityId no es clave primaria
simple”. Para dotar a un atributo con la característica de autonumerado, éste
debe ser clave primaria simple. CityId es “parte” de la clave primaria de
COUNTRYCITY12.
¿Cómo hacer, entonces, para que este atributo no tome la propiedad
Autonumber como “True” dada a su dominio Id?13 Sencillo. Se editan las
propiedades del atributo CityId (como antes se hiciera con CountryId) y se
modifica la Autonumber, llevándola a “False”. Se está cambiando, pues, el
comportamiento por defecto dado por el dominio.
Lo mismo puede decirse del atributo AttractionCategoryId que por el
momento es de Attraction y no es clave primaria, aunque pronto lo será cuando
Diego cree la nueva transacción AttractionCategory, unos pasos más adelante.
Por esta razón, decidió dejar la modificación en la definición del dominio Id
12
Para dar valores consecutivos a este atributo, habrá, pues, que apelar a una regla: Serial. Le proponemos la investigue luego
de ver algunas reglas, en la sección siguiente.
13
De todas formas, como GeneXus sabe de esta restricción, si Diego no se hubiera percatado del problema, GeneXus
igualmente hubiera actuado con inteligencia y no hubiera definido el atributo en la base de datos con autonumber. A efectos
didácticos preferimos hacer trabajar a Diego.
66
(para “encender” la Autonumber) para más adelante, cuando cree la nueva
transacción (dejando este “To-Do Diego” que, creado seleccionando el objecto
Document desde la ventana New Object, al guardarlo quedó colgado en Orphan
Documents).
67
La tercera regla también emite un mensaje, pero a diferencia de la Error, no
traba el paso del usuario hacia el campo siguiente, y permite grabar el registro.
Existen muchas más reglas, que permiten programar el comportamiento
específico que tendrá la transacción. Obsérvese que se escriben siguiendo un
estilo declarativo: salvo excepciones no importa el orden. En nuestro caso, se
dispararán de acuerdo al orden en que se encuentren los atributos
CountryName, CountryFullName y CountryDetails en el form.
En una transacción de dos niveles, se están manipulando registros de dos
tablas. Por tanto, cuando el usuario ingresa un nuevo país y sus n ciudades
mediante el form de la transacción Country, se estarán insertando n+1 registros:
1 en la tabla COUNTRY y n en la tabla COUNTRYCITY. Se insertarán en el
orden en que se hayan ingresado en el form. Puede querer dispararse una regla
inmediatamente antes o inmediatamente después que una grabación de estas se
efectúe. Para ello las reglas pueden condicionarse no sólo con cláusulas if como
las mostradas, sino también con eventos de disparo como son el BeforeInsert o
el AfterInsert.
Luego de grabarse los n+1 registros de la transacción (cabezal y sus n
líneas) se realiza automáticamente un Commit. Puede ser deshabilitado por el
desarrollador mediante una propiedad de la transacción. También existen
eventos de disparo de reglas, que capturan el momento inmediatamente
anterior a realizarse el Commit, o el inmediatamente posterior. Por ejemplo, si
quisiera realizarse un listado del país recién ingresado con sus respectivas
ciudades, podría invocarse a otro objeto GeneXus de otro tipo: Procedimiento,
asegurándose de invocarlo luego de haberse insertado los n+1 registros,
enviándole por parámetro el identificador de país recién ingresado… y si se
quiere que sea antes del Commit se escribirá en la sección de reglas:
14
Obsérvese que se está utilizando un método de invocación corriente en GeneXus: el Call, que está apareciendo aquí como una
regla. También obsérvese que lo que precede al call es el nombre del objeto invocado, en nuestro caso será otro objeto GeneXus
de tipo Procedimiento, y por último, obsérvese que los eventos de disparo aparecen identificados en la sintaxis de una regla:
Rule if condition on Event
68
Y si se quiere que sea luego del Commit se escribirá:
Sabiendo que lo único que modificó desde el anterior <F5> fue agregar
reglas a ambas transacciones, anticipa que cuando ahora vuelva a pulsar <F5>
no habrá reorganización, pero sí los pasos que siguen.
…donde condition es una condición booleana y Event es uno de los eventos de disparo válidos, que invitamos al lector
interesado a buscar en las distintas fuentes de documentación del producto. Listamos algunos: AfterValidate, BeforeInsert,
BeforeUpdate, BeforeDelete, AfterInsert, AfterUpdate, AfterDelete, BeforeComplete, AfterComplete, etc.
69
No hubo advertencias ni errores. Observe el lector cómo al elegir Country a
la izquierda, le informa las tablas en las que realizarán las grabaciones y la
integridad referencial que controlará cuando se pretenda eliminar un país, o una
ciudad a través de esta transacción.
70
completa (si hay objetos que no han variado, y fueron generados en un <F5>
anterior, ¿para qué hacerlo nuevamente?). La generación implica la escritura de
líneas de código que implementen la programación de los objetos en el lenguaje
elegido por usted para el ambiente de desarrollo (por cada una de las dos
transacciones de Diego, se generará entre otras cosas un archivo .cs. No olvide
que él eligió C# como su lenguaje de desarrollo). Además mientras el
desarrollador no cree su propio objeto principal, se creará un xml: el Developer
Menu, que contendrá links a todos los programas (objetos) generados.
4. Por último, GeneXus compila los programas y ejecuta (hasta que no se cree un
objeto principal de la ejecución) el Developer Menu en el Browser instalado en
esa máquina.
71
Le faltaba aún probar Attraction (habiendo encontrado cosas a modificar en
Country, había decidido esperar un poco), cuando vio en el Talk de la misma
que Diego le indicaba que postergara la prueba hasta que él creara la siguiente
transacción relacionada.
72
La finalidad de esta transacción es categorizar los variados puntos de
atracciones de una ciudad. Ejemplos de esto pueden ser las categorías
“Buildings/Structures”, “Nightlife”, “Relaxation”, “Adventure”, “Safaris”,
“Gastronomy”, “Business”, etc.
Con las estructuras de las transacciones Attraction y AttractionCategory
estamos diciendo implícitamente que: una “atracción” pertenecerá a una y solo
una “categoría”, mientras que una “categoría” podrá contener n “atracciones”.
El siguiente Diagrama muestra la nueva relación existente ahora entre las
tablas involucradas hasta el momento en el proyecto.
73
Observe cómo informa sobre los cambios habidos en Attraction. Y para
AttractionCategory informa que la tabla es nueva, deberá ser creada. Además
presenta una advertencia15:
15
¿Y si Mike hubiese testado anteriormente la transacción Attraction y hubiese insertado registros antes de esta reorganización?
Supóngase que hubiera ingresado una atracción con Id de categoría 1 y descripción “Nightlife”… ¿qué pasaría entonces en la
reorganización, cuando se cree la tabla ATTRACTIONCATEGORY y AttractionCategoryId pase a ser clave foránea en
ATTRACTION y AttractionCategoryDescription inferido? ¿Qué pasará con el registro pre-existente en ATTRACTION? Para
que la base de datos quede en estado consistente, tendrá que crearse un registro en ATTRACTIONCATEGORY, con Id 1… y
con descripción “Nightlife”. Esto lo hará el propio programa de reorganización. ¿Qué pasará si existían dos o más atracciones
con el mismo Id de categoría y distinta descripción? Es lo que informa la advertencia. Investíguelo.
74
Tras el [Reorganize] confirmado por Diego, obtuvo la nueva base de datos y
los nuevos programas generados. Cerró el Browser (pues la tarea de testar todo
era de Mike) y finalmente documentó la nueva transacción y los cambios de
Autonumber.
Mike leyó lo escrito por Diego y se dispuso a prototipar todas las
transacciones. <F5> nuevamente…
Para que el usuario final no tenga que recordar códigos para las claves foráneas,
GeneXus crea objetos automáticamente que se conocen como Prompts y que se
invocan mediante la imagen que aparece en ejecución al lado de todo campo
clave foránea. Se trata de listas de selección que muestran toda la información de
la tabla referida, para que el usuario seleccione la deseada.
75
Con esa lista abierta, Mike elegirá la primera opción,
“Buildings/Structures”, y el control volverá a la transacción Attraction, donde
aparecerá en el campo AttractionCategoryId el valor 1, correspondiente; el foco
estará allí, y el usuario podrá pasar a ingresar el siguiente campo (en nuestro
caso, el Id de país, para lo que el procedimiento podría repetirse).
También probó que estuvieran funcionando bien los controles de integridad
referencial, por lo que colocó en AttractionCategoryId un valor inexistente de
categoría, y vio cómo en ejecución aparecía un mensaje de error
indicándoselo16.
16
Segundos más tarde, probaría intentar eliminar en la transacción AttractionCategory una categoría con atracciones asociadas,
esperando ver que fallara la eliminación, asegurándose así la integridad referencial. Amigo lector: pruebe y verá.
76
Para que el usuario final no tenga que estar abriendo estas listas de
selección cada vez que tenga que ingresar el valor de una clave foránea,
GeneXus provee de la facilidad de “disfrazar” en ejecución un atributo por
otro. Mike propuso esto último; Diego lo realizó cambiando dos propiedades
del atributo AttractionCategoryId, y como resultado, ahora para ingresar la
categoría “Buildings/Structures” Mike sólo debe recordar con qué letras
empieza, digitarlas y tendrá autocomplete:
77
Ahora quedaba mejorar un poco la usabilidad y la apariencia de lo que iba
del proyecto, y dejaron por escrito que habría que comenzar a hacer uso de
algunas de las otras herramientas de GeneXus, como los GXpatterns. Y tal vez
ya podrían reunirse todos para discutir si poner en producción alguna parte de
la aplicación en el sector Tourism de la agencia, de tal forma que pudieran ir
probando, cargando países y ciudades y enviando de retorno las sugerencias
que encontraran.
78
Capítulo 3
Hecho a máquina, terminado a mano
Planes Inmediatos
El proyecto de Travel Agency & Co. ya ha avanzado lo suficiente como
para que varias de sus partes sean puestas en preproducción. Es momento de
pruebas por parte de los usuarios y a la vez de seguir incrementando el sistema.
79
por su atributo más significativo, el nombre17, sino que además permite filtrar
en ese grid también por nombre (a medida que el usuario final va digitando en
la variable de filtro, automáticamente se va filtrando en el grid), así como
pagina automáticamente el grid18:
Para
desplazamiento
entre las páginas
del grid
17
Ver página 32. Cuando se ingresan los atributos en la estructura de una transacción, el primero de tipo Character o VarChar
ingresado, se marca como el descriptor. Se puede cambiar de descriptor posicionándose en la estructura sobre el nuevo atributo
descriptor, y –vía botón derecho- eligiéndolo mediante “Toogle description attribute” en el menú contextual desplegado.
Aunque como veremos apenas más adelante, Julia tendrá otra alternativa para hacerlo…
18
El valor por defecto es de 10 registros por página (es decir, carga 10 líneas del grid). Lo hemos modificado a 3 en un lugar
centralizado del Pattern Work With del que luego hablaremos.
80
Y no sólo eso, también permite realizar acciones tales como:
19
El objeto Transacción tiene una variable predefinida, &Mode, que identifica el modo en que se encuentra en un momento
dado (modo Insert, modo Update, modo Delete o modo Display) de acuerdo a la operación que se vaya a realizar. Si se le envía
por parámetro el modo y se recibe en esta variable, entonces la transacción sabrá qué operación se quiere realizar.
81
Es decir, se creó un verdadero “Trabajar con Países” (Work With
Countries). Lo mismo podrían pedir los usuarios para trabajar con las otras
entidades: atracciones y categorías de las atracciones.
¿Patrones?
En líneas generales usted ya sabe de qué se habla cuando se dice patrón:
conocimiento común que puede ser aplicado a situaciones distintas. Un pattern
en GeneXus es muy potente; evita que usted deba crear “manualmente” objetos
de gran versatilidad funcional; permite el reuso del conocimiento; y es un
utilitario abierto: llegado el caso, usted mismo puede crear sus propios
patrones. Introducen un alto nivel de productividad y generan aplicaciones de
calidad, con interfaces más uniformes, y una lógica consistente. Es un
generador de conocimiento a partir de conocimiento.
82
Todo lo que Julia hizo fue aplicar el patrón Work With (uno de los
existentes) a la transacción Country, obteniendo un trozo de aplicación que
implementa todo lo necesario para poder “Trabajar con…” la información
asociada, en nuestro caso: “países”.
Podemos pensar al patrón Work With (así como muchos otros) como una
máquina, cuya entrada es una transacción (y la KB) y cuya salida es un
conjunto de nuevos objetos GeneXus (algunos de los cuales mostramos antes
en ejecución), más cambios en algunas propiedades de la KB, más la propia
transacción modificada20…
1. En el grid sólo queremos ver el nombre del país y la región del mundo (el
resto de la información no nos interesa).
20
En particular la transacción deberá declarar que recibe parámetros, dado que ahora se la quiere poder invocar desde el objeto
Work With Countries (WWCountry).
83
3. …filtrar el grid por región del mundo (además de por nombre)”.
84
Configura el
web panel
WWCountry:
Configura el
web panel
ViewCountry
Esta imagen muestra una “instancia” del pattern Work With, la instancia
para Country, que permite personalizarlo21. Se puede observar una estructura
jerárquica conteniendo nodos y un conjunto de propiedades cuyos valores
determinarán la apariencia y el comportamiento de lo generado por el pattern
para esta instancia.
El pattern Work With generará, entre otras cosas, dos nuevos objetos
GeneXus, que pudimos ver en ejecución, y que hemos incluido para usted sobre
la imagen de la instancia, de un tipo del que aún no habíamos hablado: Web
21
No hemos definido “instancia”. Puede pensarse como la declaración de cómo se aplicará el patrón al caso particular de este
objeto.
85
Panel. Permite, grosso modo, hacer consultas interactivas a la base de datos, de
modo amigable22.
En la imagen hemos indicado qué nodos de la instancia del pattern
permiten configurar las características de qué web panel. Los usuarios le han
pedido a Julia modificaciones sobre el primer web panel. Nada han dicho por el
momento del segundo (pero tal vez lo hagan…).
Observe el lector lo primero que aparece bajo el nodo “Level (Country)”.
Recuerde que CountryName era el atributo descriptor de la transacción (el
de mayor carga semántica). ¿Qué repercusión tiene el atributo descriptor en lo
generado por el pattern? Mucha. Por ejemplo, define cuál será el atributo del
grid del WWCountry que contendrá un link hacia el web panel ViewCountry
creado automáticamente para ver la información de ese país. Pero este es sólo
un ejemplo, como veremos, se utiliza en varios lugares. No obstante, el atributo
descriptor, que es heredado de la transacción, si bien puede ser cambiado por
otro en el árbol, no implicará que sea también cambiado en la estructura; es
sólo el descriptor para el pattern.
86
ese nodo, con <Supr>, el resto de los atributos, con la excepción de CountryId.
Este último no puede ser eliminado tan alegremente. Aunque no se le muestre
al usuario final en el grid, deberá estar presente, invisible, como justificaremos
en breve. Por lo tanto, lo que hará Julia en ese caso será posicionarse sobre el
atributo en ese nodo, y, editando sus propiedades con <F4>, modificar la
propiedad Visible pasándola a “False”.
Así quedará la instancia; vea la repercusión en ejecución.
¡Requerimiento 1 listo!
El subnodo Orders indica el orden por el que en forma predeterminada se
desean recuperar los datos que se cargarán en el grid. El orden elegido por
defecto es (¿casualmente?) el de CountryName, el atributo descriptor. Los
usuarios pidieron poder elegir entre ese orden y uno por CountryWorldRegion,
así como uno compuesto por ambos.
¿Cómo puede el usuario decidir en ejecución por cuál criterio quiere
ordenar la información a recuperar? Mediante un combo box que le muestre las
posibilidades. Julia necesitará agregar los nuevos ordenamientos pedidos bajo
el subnodo Orders, a los que les dará un nombre (World Region, Both) que
87
serán las opciones que el usuario final verá en ejecución en el combo insertado
automáticamente por el pattern.
Así quedará la instancia y luego el web panel generado (en ejecución):
88
WWCountry que GeneXus implementó y la instancia en la página 77. ¿Y si
ahora le mostramos lo que hizo Julia en la instancia y la repercusión que esto
tuvo en el web panel?
24
Ya habrá deducido que las variables aparecen identificadas en todo el código GeneXus por un ‘&’ que es colocado como
prefijo del nombre de la variable.
25
Recuerde que el atributo CountryWorldRegion está basado en el dominio WorldRegion de tipo enumerado. De ese modo
cuando se presenta como control en un form, será un combo box. Aquí se trata de una variable que se basa en este atributo y
observe cómo hereda el mismo comportamiento, sin tener que decirle nada.
89
transacción Country, editándose el país seleccionado del grid. Para ello el
pattern WorkWith tuvo que modificar las reglas de la transacción, agregando
algunas que se suman a las que ya habíamos declarado. ¿Para qué? Para poder
instanciar, editar el país, recibirlo por parámetro. Echemos un vistazo:
La forma que tiene todo objeto GeneXus de declarar los parámetros que recibe y
devuelve, es mediante la regla Parm.
Cuando desde el “Work With Countries” se elija un país del grid para
modificarlo, se invocará a esta transacción enviándole el modo ‘UPD’ que es
recibido en la variable &Mode y se enviará además el CountryId
correspondiente (es por este motivo que tuvimos que dejarlo en el grid, oculto).
Cuando se quiere insertar, se envía ‘INS’ y el valor vacío como segundo
parámetro, y así…
90
Web panel ViewCountry:
Nodo View (Country Information)
Cuando hacemos clic en ejecución sobre el nombre del país, se abre el web
panel ViewCountry que nos muestra la información del país correspondiente
(ver página 74). Observemos qué datos nos muestra: el nombre del país
recibido por parámetro (¿otra vez casualmente CountryName?) y luego en un
tab General todos los atributos del primer nivel de la transacción Country para
ese país, y dos botones para poder llamar a la transacción pasándole ese país
por parámetro (para modificar el país y eliminarlo respectivamente) y en un tab
City, nos muestra en un grid todas las ciudades del país.
26
COUNTRY sólo tiene a COUNTRYCITY en esta relación (muchas ciudades por cada país) y por ello sólo aparece un Tab.
Obsérvese que el nombre que eligió GeneXus para nombrarlo fue la descripción del nivel 2 de la transacción Country (la
descripción de las líneas, que era City).
91
Concluyendo: Cuando Julia marca “Apply this
pattern on save” en el selector Work With de
Country, al grabar, se generarán objetos GeneXus
como los que estuvimos viendo en ejecución,
pudiendo acceder a ellos en el Folder View, bajo la
transacción. Asimismo, cada vez que modifique la instancia, al grabar, se
regenerarán los objetos que corresponda, en forma automática.
¿Más?
Los usuarios, que por momentos parecen tener como especial misión en el
mundo encontrar las flaquezas de los desarrolladores y volver sus vidas
miserables, pidieron a Julia que luego de modificar un país, no se volviera al
llamador, WWCountry, sino que les mostrara toda la información del país
recién modificado (es decir, ir al “View Country”).
92
With AttractionCategories). Si en la mayoría o todos los Work With de la
aplicación queremos que el comportamiento por defecto sea que luego de
modificar se vaya al View, entonces en lugar de cambiar a mano una por una
esta propiedad en la instancia de cada transacción, podemos cambiar en un
lugar centralizado el “<default>”.
¿Cuál es este lugar centralizado?
Se encuentra bajo el grupo Preferences de la ventana Knowledge Base
Navigator: nodo Patterns. Observe en la imagen que sigue que al momento
sólo se encuentran los patterns Work With y Category. Pero ya habrá otros de
usted, de Artech y de terceros…
(“Extensibilidad”… “integrabilidad”… piezas claves en la ideología X)
93
registros por página de los grids (cosa que ya habíamos hecho sin mostrarle,
pasándola a 3), tenemos el nodo Grid27. Invitamos al lector interesado a
investigar sobre las configuraciones generales que puede realizar desde aquí.
Por ejemplo, puede cambiar el aspecto general de las páginas, los nombres de
las mismas, las imágenes para las acciones de Insert, Update, Delete y mucho
más.
Reflexione el lector sobre lo que significa “trabajar con”. ¿Qué otras cosas
podría pedir el infame usuario? (perdón lector, es que también somos
humanos). Pregúntele a Julia y ella le podrá contar que en esa misma reunión,
se le ocurrió a esa especie particular de homo sapiens que quería:
A. Exportar la información de los países con los que estaba trabajando, a una
planilla Excel. ¿Puede usted creer? Además dijeron que no querían:
B. Que se pudiera eliminar países desde el Work With Countries. Y por si esto
no fuera poco, que querían:
C. Agregar otra acción más sobre los países (además de las de Insert y
Update ), cuyo efecto fuera mostrar en otra pantalla cada país con sus
ciudades, ¡todos juntos!, y en un formato más agradable a la vista. (Un
poco ambiguo para nuestro gusto… y el de Julia)
Julia disfrutó con sorna: ante cada requerimiento que el usuario pedía con
ojos maliciosos, hacía un par de clics y listo. Salvo para el último, donde el
usuario logró su aparente objetivo: complicarle la vida. Pero ni tanto. No
olvidemos que Julia no es técnica, luego en la oficina pediría ayuda a Diego.
27
Si observa en su KB la propiedad Page del nodo Grid, encontrará que tiene el valor “Page.Rows”. ¿Qué es Page? Pues un
dominio enumerado creado automáticamente, que contiene un solo valor, Rows, cuyo valor por defecto es 10 y que nosotros
hemos cambiado a 3.
94
Acciones en el Work With Countries
95
cualquier “trabajar con”. Aquí es donde deberá intervenir Diego,
implementando en GeneXus ese objeto. Julia escribe en el selector
Documentation del WorkWithCountry que esto queda pendiente (¿qué selector
Documentation? Observe el tab que se abre cuando usted presiona doble clic
sobre el nodo WorkWithCountry del Folder View, bajo la transacción Country
-vea página 84 y la siguiente página- … todos los caminos conducen…). Lo
hizo bajo un título “To-Do Diego” para que sea implementado por él cuando
ponga manos a la obra28.
28
Como verá un poco más adelante, a Diego no le resultará nada complicado ubicar esta nota de Julia entre toda la
documentación que se supone “desperdigada” entre tantos objetos de la KB, y será gracias a las Categorías Dinámicas, de las
que él mismo hablará en el capítulo 6.
96
Obsérvese que en el diálogo de propiedades se le debe necesariamente dar
un nombre y asignar un dominio a la variable que se está insertando (es lo que
indica el ícono ). Diego le asigna como nombre ShortDetails y como
dominio el recién creado de igual nombre. Solamente resta decirle el valor que
deberá tomar para cada línea del grid.
- Como bien intuyes, Julia, cuando ponías los atributos bajo este nodo
no era preciso decir más: GeneXus recorrería la tabla COUNTRY, y para cada
registro que cumpliera con las condiciones de los filtros especificados (en este
caso veo que son por CountryName y CountryWorldRegion), cargaría una línea
nueva en el grid del “Trabajar con” con los valores de esos atributos en cada
columna. Si los usuarios quisieran la descripción completa del país, bastaría
con incluir el atributo CountryDetails, pero lo que quieren es acortar la
descripción a 70 caracteres. Por lo que, lo único que tendremos que hacer es
especificar en la propiedad LoadCode de la variable que ingresamos, el valor
que tomará para cada línea: será la operación de quedarnos con los primeros 70
caracteres del string29, es decir: &ShortDetails = Substr(CountryDetails,1,70).
Sí, CountryDetails es el atributo. Así de sencillo. Ya puedes hacerlo sola.
- Entiendo, y entonces para el otro requerimiento, el de mostrar el
número de atracciones que tiene cada país…
29
GeneXus cuenta con múltiples funciones para trabajar con los distintos tipos de datos. En particular, tiene la función substr,
que permite recuperar un substring de un string dado.
97
- Bueno, en principio ahí el procesamiento que debes hacer para cargar
la variable que contendrá esa información es un poco más complejo. Sólo en
principio. Fíjate que para el país que se esté cargando en una línea del grid en
cada oportunidad, se deberán recorrer todas sus atracciones para contarlas.
Tienes tres caminos: uno es programar ese código que te permite recorrer
información30, otra vez en el LoadCode; otro, es agregar un atributo a la
transacción Country, que contenga esa información, suponte un
CountryTotalAttractions. De esta manera sólo tienes que agregar este atributo
al nodo Attributes de la instancia, y ¡listo!
- Pero, espera, no entiendo… En ese caso, cuando el usuario ingrese la
información de un país mediante la transacción, ¿va a digitar el número de
atracciones que contendrá? ¿Y si después resulta que no coincide con las
atracciones reales que ingresó? Eso no les va a gustar a los usuarios.
- ¡Julia, en cualquier momento me dejas en la calle! Exactamente, no
tiene sentido que el usuario tenga que digitar una cantidad que es resultado de
un cálculo entre valores que están en la base de datos. Este atributo,
CountryTotalAttractions será de un tipo especial, que en GeneXus se denomina
fórmula. Verás, ingresaré ese atributo nuevo en la estructura de la transacción
Country, y le diré a GeneXus que tengo más información, que sé cómo debe
calcularlo, y que por tanto no lo almacene en la base de datos, sino que siempre
que se le pida su valor, la calcule como yo le digo:
30
Será el comando For Each, del que hablaremos más adelante, en el Capítulo 5.
98
- ¿Count(AttractionName)? Ajá, con eso le dices a GeneXus que
quieres que para ese país cuente la cantidad de valores de AttractionName
asociados31, ¿no? Ahora, ¿cómo sabe que tiene que contar las atracciones de
“ese” país y no de “todos” los países?
- Bueno, recuerda que GeneXus conoce las relaciones entre los datos y
sabe que una atracción pertenece a un país y ciudad determinado. Y cada país-
ciudad pertenece en particular a un país. Con eso basta.
- ¿Y qué ventaja tiene esta solución para nosotros, frente a la otra, la de
agregar una variable en la instancia del pattern y decirle cómo hacer el cálculo
con ese comando for each que dijiste -cosa que, por otra parte, no sé hacer-?
- Pues que aquí has implementado una solución general. Si en otros
lados, en otros objetos, cuando recuperas un país necesitas saber su cantidad de
atracciones, sólo con nombrar el atributo fórmula ya se dispara el cálculo, y no
tienes que programarlo a mano. Ahora sólo tienes que agregar al nodo
Attributes de la instancia este atributo fórmula. ¡Ya están listos los dos
requerimientos del e-mail!
31
Para usted que sí es informático: para cada país contar la cantidad de registros asociados de la tabla donde se encuentra
AttractionName, que en este momento es ATTRACTION.
99
- Pero espera… habías dicho que teníamos tres caminos posibles para
implementar esto último y sólo me has explicado dos.
- Qué suerte, Julia. Estaba esperando que te dieras cuenta. De hecho, el
tercer camino es el que yo hubiera utilizado desde un principio en este caso. Si
no lo hice fue para aprovechar a explicarte esto de las fórmulas globales. Lo
que hicimos sería lo más recomendable si creyéramos que muchas veces, y en
lugares distintos, vamos a tener que realizar esa misma cuenta de las
atracciones. Pero si por el contrario, será en pocos lugares, tal vez lo mejor
hubiese sido directamente definir la variable como antes en el nodo Attributes
de la instancia del Work With, por ejemplo &CountryTotalAttractions, y en la
propiedad LoadCode de la variable, especificar &CountryTotalAttractions =
count( AttractionName ).
- ¿Pero no es la misma fórmula que antes?
- Sí y no. Aquí no has definido un atributo fórmula, sino que estás
utilizando la fórmula localmente, como si fuera una función. Por el contexto en
el que está especificada -en nuestro caso estará en un grid que recorre la tabla
de países- sabrá que deberá contar solamente las atracciones del país que se
está cargando en cada caso.
Así mostró a Julia, tras el <F5> cómo luce ahora el “trabajar con países”:
100
Quod Pattern non dat, GeneXus praestat32
En lo que Julia demoró en ir a servirse un café, saludar a un compañero y
volver con la taza humeante, Diego ya había implementado el último
requerimiento (el C pendiente). Como no estaban seguros si implementaba lo
que deseaban los clientes, capturaron la siguiente pantalla y se la enviaron por
e-mail:
32
N. de A. Frase latina, “Quod Natura non dat, Salamantica non praestat” para referir a que si la naturaleza no da inteligencia,
ni la prestigiosa Universidad de Salamanca puede darla.
101
- ¿Cómo lo hiciste tan rápido, Diego? ¿Me enseñas? No puede ser
complicado.
- No lo es; lo que hice fue crear un nuevo objeto en GeneXus, de tipo
Web Panel. Es el que estás viendo en la pantalla en ejecución. Fíjate que lo
que deseas es mostrar información de países y de ciudades (las suyas). Olvídate
por un momento de los países, y supón que sólo quieres listar información de
las ciudades, en un grid. Lo único que debes hacer es insertar un control de este
tipo en el form del web panel y decir qué atributos compondrán sus columnas.
Sólo con hacer esto, cuando presiones <F5> verás en ejecución listadas todas
las ciudades existentes. GeneXus observa los atributos presentes y de ellos
infiere la tabla que deseas navegar (le llama tabla base). Fíjate –creó un web
102
panel “Cities” en un segundo, insertó el grid mediante la toolbox que ofrece
todos los controles que pueden insertarse en un form, eligió el atributo
CityName, y quedó el form del objeto como puede ver a continuación- en
nuestro caso: los atributos que colocamos en el grid (CityName) son de la tabla
COUNTRYCITY y esa será la que navegará GeneXus.
103
- Observa que ahora aparece el grid de ciudades, junto con los atributos
CountryFlag y CountryName dentro de un rectángulo. ¿Qué es? Otro tipo de
grid, uno llamado Free Style, pues como ves, la información del país que
quieres listar se muestra no por columna, sino toda junta, con el diseño que tú
decidas. Pero también es un grid. Mostrará por tanto, tantos países como
existan en la tabla de países. Observa que en sus propiedades aparece una
Columns, a la que modifiqué el valor de “1” a “3”, y es por ello que puedes
ver en la imagen que le enviamos a los usuarios tres países por fila. Rows “0”
significa que no realice paginado, sino que muestre todos los países
existentes. Luego observa que dentro del Free Style, podemos colocar
cualquier control, incluyendo otro grid. Y al hacerlo, GeneXus encuentra las
104
relaciones entre las tablas, y es por esta razón que está listando por cada país,
sus ciudades en el grid, sin tener que indicarle nada, lo hace solo. Si no te
convence, míralo tú misma en el listado de navegación que aparece luego de
la especificación, tras el <F5>. A ver si lo entiendes…
105
ese país, como una galería de imágenes que se deslizan. Quisiera hacer algo
que impresione… – quedó pensativo.
- ¿A los usuarios?… Bueno, Diego, veo que hoy no estás para bromas.
Antes de que sigas, quedó pendiente poder llamar desde el Work With
Countries a este web panel que acabas de implementar.
- Es verdad, pero eso sabes hacerlo. Simplemente debes agregar una
nueva acción a las ya predeterminadas (insert, update, etc.) en la instancia
del Work With de Country: la asociaremos a un botón fuera del grid y le
indicaremos que en esa acción queremos invocar al web panel
“CitiesPerCountry” que acabo de crear.
Sobre el nodo Selection (Work With Countries), con botón derecho,
seleccionar “Add actions” y luego posicionarse sobre el nuevo nodo Action y
otra vez repetir la operación. Vea lo que hicieron nuestros amigos, y la
repercusión en ejecución:
106
el web panel creado…
¿Botón dentro del grid?
¡Requerimiento C listo!
107
En todo momento se mostrarán 3 imágenes, siendo la del centro la activa.
Cuando se pulsa sobre la de la derecha, la del medio pasa a la izquierda, la de la
derecha al centro (pasa a ser la activa) y aparece una nueva a la derecha.
Para lograr esto, Diego agrega un nodo Tab en la instancia del pattern Work
With correspondiente al View, que se cargará con el contenido de otro objeto
GeneXus que luego deberá programar. Podría ser un web panel, pero en
realidad tiene que ser un objeto que se pueda ejecutar dentro de otro (en nuestro
caso del ViewCountry). Se tratará de un Web Component. A los demás efectos
será igual que un web panel. Le llamará CountryAttractions, y deberá indicar
en el nuevo Tab que se cargará con el mismo:
108
Se le deberá pasar el CountryId como
parámetro al Web Component que
implementa la galería de imágenes.
Además de los controles estándares que usted suele insertar en sus paneles
(attribute, grid, text block, etc.), GeneXus X le permite crear sus propios
controles personalizados: user controls. Una vez que un control de usuario es
instalado, puede ser utilizado como cualquier control estándar. GeneXus X
viene con algunos controles de usuario ya instalados (vea la toolbox en el IDE;
ellos se encuentran agrupados bajo el rótulo User Constrols): uno de ellos es el
ImageGallery. Observemos en la foto composición que sigue que Diego insertó
un control de ese tipo en el form del web component, y luego lo único que tuvo
que hacer fue configurarle sus propiedades. Al insertar este control en el form,
se crea automáticamente un tipo de datos estructurado, ImagesData, más
conocido como SDT por su sigla en inglés, que en forma flexible permite
construir listas de largo variable de records. En este caso, una lista de
elementos en el que cada uno tendrá la información de la atracción a mostrar;
esto es: su id, el lugar donde se encuentra la imagen (en general se almacena la
109
imagen en tamaño grande, y la misma imagen en tamaño pequeño) y la
descripción de la misma. Junto con el SDT se crea automáticamente la variable
&imagesData basada en el mismo, justamente para cargar las imágenes a
mostrar.
Por tanto, lo único que Diego debió hacer fue programar la carga de esa
variable. ¿Cómo lo hizo? En el evento Start del Web Component escribió la
invocación a un objeto que creó inmediatamente, que justamente tiene por
objeto cargar estructuras jerárquicas de datos, como SDTs. Ese objeto es de un
tipo que veremos más adelante (el Data Provider). No importa que ahora no
entienda este código. Sólo queríamos mostrarle lo fácil que es lograr este
requerimiento.
110
¿Look & Feel consistente, sin transpirar?
“Tener un look&feel consistente es hoy en día un deber de toda aplicación
Web. Crear y mantener cada página de una aplicación Web asegurando la
consistencia con el resto del sitio toma gran tiempo de programación” leyó
Julia en voz alta de una revista técnica que estaba sobre la mesa del escritorio
de Diego.
111
- No con las Master Pages –acotó Diego-. Como te conté, un web
panel puede implementar una página web común y corriente, como la que
hicimos, CitiesPerCountry -con los grids Free Style y estándar-, es decir, una
página web independiente que será invocada por otra -en nuestro caso por
WWCountry-; o puede implementar una página web que será parte
componente de otra, como la de la galería de imágenes que era un Web
Component parte del ViewCountry; o por último, puede ser una página
maestra, es decir, una página que contiene centralizado allí lo que será común a
todas las páginas del sitio,
como el encabezado y el pie
de página que has visto por
todos lados repetido;
incluso suelen tener un
menú vertical a la izquierda, de manera tal que sólo esté en un lugar, y que si se
quiere modificar, se lo haga en un solo sitio.
- ¡Con razón! Cuando empezamos con el proyecto vi a Mike ejecutando
las transacciones y no entendía cómo era que todas salían con el mismo
encabezado y pie de página (que también veo ahora que tienen el Work With
Countries, y el View Country) siendo que en el form de esos objetos esas partes
no aparecían. Existe una master page que programa ese look común, ¿verdad?
- Efectivamente. Mira esto- dijo
Diego a la vez que seleccionaba la
transacción Country y accedía a sus
propiedades-. Verás lo mismo en cada
transacción y en los web panels creados por
patterns: AppMasterPage es una master
page creada automáticamente con la KB
-observa el contenido de la carpeta
GeneralWeb- que es la que contiene ese
encabezado y pie de página que se repite por
112
doquier. Si abrimos este web panel, mira sus propiedades. ¿Qué te dice esto?
113
114
Capítulo 4
Vuelo al mundo de los subtipos
El proyecto avanzaba según los tiempos estipulados. Cada uno en lo suyo.
Dada la filosofía incremental de GeneXus, no temían a los cambios ni a lo
nuevo. El modelo amparaba todo lo necesario. Sólo lo necesario.
115
tipo. Yo estoy en las listas de varios de ellos, y me considero un miembro
activo.
- Suena lindo. Pero, ¿funciona? O sea, ¿te dan una mano desinteresada
cuando la necesitas, o es puro cuento?
- Es que es hoy por mí y mañana por ti. Todos necesitamos una mano
de vez en cuando. Si en vez de vernos como competidores nos sentimos
cooperadores, trabajamos mejor, más animados, llegamos a mejores soluciones,
para todos. Tiramos para un mismo lado: hacer crecer a la Comunidad, en
beneficio de todos. Puede resultarte un poco ingenuo, pero la prueba está a la
vista: funciona. Como evidencia de ello, el propio GeneXus está compuesto por
extensiones de GeneXus. Es decir, la Comunidad puede seguir haciendo crecer
a GeneXus con sus propias extensiones. De hecho, GeneXus puede
considerarse en sí mismo una extensión.
Tras reparar en esa luz antigua, conocida, que adquiría su mirada, continuó
más seguro:
- Mira – invitó Diego al tiempo que abría la ventana Extensions
Manager a través de la opción Tools del menú de GeneXus –. Esta es la lista de
extensiones que componen GeneXus X en este momento. La mayoría han sido
desarrolladas por Artech y las otras por personas de la Comunidad que las
publican para que diferentes miembros puedan hacer uso de ellas.
- Veo que en la Start Page aparece una sección que dice “Extensions”,
con un abstract escrito por el autor, y un link para instalarla.
- Sí, y si te fijas, aparecen
unas cuantas que no dicen By
Artech. Son extensiones realizadas
por terceros, miembros de la
Comunidad.
En cuanto al proyecto en sí y al
punto en que habían llegado, la realidad reclamaba otras transacciones. Tras
una breve ojeada a lo ya hecho, Diego pasó a crear Airline. Su finalidad:
116
almacenar el nombre de las compañías aéreas involucradas con Travel Agency
& Co.
33
Recuerde que estamos simplificando la realidad: supondremos que no habrá nunca más de un aeropuerto por ciudad.
117
…podríamos
decir que los
atributos CountryId
y CityId en la
transacción Flight
representan una
ciudad de un país,
pues se llaman igual que los identificadores de la transacción Country. Es decir,
serán una clave foránea a la tabla COUNTRYCITY. Hasta allí nada nuevo bajo
el sol, pero ahora debemos agregar otra ciudad34, en otro rol, el de “ciudad de
destino”. Es decir, tiene que aparecer una doble referencia a una ciudad: en un
caso en un rol de ciudad origen, y en el otro de ciudad destino. ¿Cómo hacemos
esto? GeneXus no nos permitirá agregar la misma pareja de atributos en la
misma transacción, eso es obvio porque ¡¿cómo se discriminaría entre un
atributo y otro?! Pero podríamos hacer algo así –tomó el lápiz y escribió dos
atributos nuevos, luego de lo cual antecedió a los dos primeros con la D de
“departure” y a los dos últimos con A de “arrival”.
- Hmm… pero si GeneXus entiende que si se llaman distinto son otra
cosa… ya no habría conexión entre vuelos y ciudades, ¿verdad?...
- Verdad. ¿Solución? Poder decirle a GeneXus que si bien se les ha
cambiado el nombre a los
atributos, por la necesidad
de diferenciarlos, de todos
modos corresponden al
mismo concepto. Es decir,
que no se confunda: que
por más que el atributo
DCountryId no se llame
34
Cuando hablamos de “ciudad”, nos referimos a una ciudad real, como París. Para identificar una ciudad, necesariamente
debemos dar el par {CountryId, CityId}. Está sobreentendido. Por el diseño de las transacciones, una ciudad no tiene existencia
independiente del país al que pertenece. Así, existe ciudad Rosario de Argentina y ciudad Rosario de Uruguay. Pero no existe
Rosario, independiente del país del que se trate.
118
CountryId, a todos los efectos es como si se llamase así, de modo que siga
estableciendo las relaciones de integridad referencial igual que antes. Decirle
esto a GeneXus es decirle que DCountryId es un subtipo de CountryId35. La
solución completa, por tanto, es agrupar estas parejas en dos grupos de
subtipos, ¿ves? – inquiría mientras rearmaba el croquis.
35
Recíprocamente, decimos que CountryId es supertipo de DCountryId
119
subtipos adecuado” – pensó Mary, pero no emitió palabra –. Muéstrame cómo
se definen estos grupos en GeneXus, anda; así lo termino de entender.
Diego seleccionó el icono Subtype Group de la ventana New Object, y le
asignó por nombre “Departure”. Mary mantenía un silencio cómplice…
120
en FlightDepartureCountryName. Ahora habría que crear el otro grupo de
subtipos, el de los arribos, ¿verdad?
- Bueno, no necesariamente, pues ya están claramente identificados los
“dos” grupos; uno es el explícito, el que acabo de crear. Y el otro está implícito
si yo coloco los atributos CountryId, CityId en la estructura de la transacción37.
Pero por claridad, solemos en casos de este tipo mantener todas las ocurrencias
en grupos, porque si no uno de los roles, en este caso el de ciudad de arribo, no
quedaría claramente identificado. Te invito a que tú sola armes el otro grupo,
¿te animas? –preguntó Diego, señalando con su mano el laptop.
- Bueno, veamos… – dijo ella controlando su impulso de abalanzarse
sobre el teclado. Tipeó, tipeó y tipeó, hasta que…
37
Sólo con cambiarle el nombre a una de las dos ocurrencias de CountryId y de CityId alcanzaría para eliminar la ambigüedad.
121
Aires, corresponde a un vuelo de la aerolínea Pluna de los sábados de mañana.
Si ese vuelo cambiara para la tarde, también cambiaría su número de vuelo.
La siguiente imagen muestra la estructura final. Observe usted el ícono
que indica que el atributo es un subtipo y además parte de clave foránea; y el
ícono que indica que es un subtipo, por un lado, e inferido, por el otro.
Viendo que el atributo FlightClassType sólo puede tener hasta tres posibles
valores fijos: “Business”, “Standard” y “Executive”, Diego creó un dominio
enumerado para él.
Antes y después
122
Mary no pudo ocultar su cara de
decepción. Diego, que estaba
pendiente de todas sus reacciones y
que también percibió un form un poco
pesado de ver, agregó
inmediatamente: “vamos a arreglar
este form que está muy poco
amigable”.
Para hacer el ingreso de país de
partida y ciudad de partida más
amigable para el usuario, podría
utilizar combo boxes dinámicos, o
disfrazar al Id del Nombre (recuerde
lo que hizo en la página 69) y lo
mismo con el país y ciudad de
llegada, así como con la aerolínea.
123
- Bueno, sí, eso es lo que quieres, pero por eso mismo, allí deberás
colocar el atributo CountryName y no el subtipo, porque en verdad es así como
se llama el atributo en la tabla COUNTRY y no FlightDepartureCountryName.
Así, Diego escribió CountryName en
la propiedad ItemDescriptions y
también modificó el valor de la
propiedad ContextualTitle del atributo
FlightDepartureCountryName que ahora
dice “Departure country”. Hizo análogas
acciones para FlightDepartureCityId,
para FlightArrivalCountryId y para
FlightArrivalCityId. Observe cómo luce
ahora el form, sin que Diego tocara nada
en él.
Hizo lo mismo para Airline, y
además editó las propiedades del grid en
el form, modificando el valor de Rows,
pasándolo de 5 a 0. El valor de esta
propiedad especifica la cantidad de
líneas vacías que se presentan en
ejecución para que el usuario ingrese los
datos allí. Obsérvese que no es necesario
tener ninguna línea vacía, dado que
abajo del grid aparece [New Row] para
agregar una línea vacía al grid y poder llenarla.
Nuestro amigo hizo <F5> para mostrarle a Mary en ejecución el “después”:
124
...cuando ésta sorpresivamente anunció que
debía irse.
- Mil gracias, Diego. Ahora
discúlpame pero debo irme, te llamo
en estos días”.
125
reservas con sus nombres y números de asiento. Aquí el identificador sí será
numérico y autonumerado.
count(FlightInstancePassengerSeatNumber)
Con este diseño, FlightNumber será una clave foránea. ¿Cuántos FlightInstance
podrán haber para el mismo vuelo, FlightNumber? ¿Corresponde a lo deseado?
Existen algunos atributos aquí que merecen especial atención. Por ejemplo,
FlightDepartureTime38 es la hora de partida estipulada del vuelo (es inferido de
FlightNumber), mientras que FlightInstanceDate y FlightInstanceTime son la
fecha y hora reales, del vuelo real. La hora del vuelo real, se inicializa en forma
predeterminada con el valor de FlightDepartureTime, pudiendo ser cambiada
por el usuario, para lo que Diego declaró en las Rules:
default(FlightInstanceTime, FlightDepartureTime);
Esta regla sólo se ejecuta cuando el modo es Insert. Por otro lado, observe
la otra regla que ha agregado Diego:
38
Si bien los atributos FlightDepartureTime y FlightInstanceTime son de tipo DateTime (permiten almacenar una fecha+hora),
sólo nos interesa la representación de la hora.
126
El atributo FlightTotalCapacity es inferido a través de FlightNumber. ¿Y
FlightInstanceNumberOfPassengers? Es una fórmula que cuenta la cantidad de
líneas que se han ingresado en el subnivel. Cada vez que se agregue un nuevo
pasajero en el grid del form (que no hemos mostrado pero que usted imaginará
sin problemas), se estará agregando un nuevo número de asiento para ese vuelo
real, FlightInstancePassengerSeatNumber, por lo que la fórmula se actualizará,
sumando 1, y si con ese pasajero se ha superado la capacidad, se disparará el
error que impedirá que se inserte en la base de datos el pasajero para ese vuelo.
Por último, Diego declara la regla:
127
El siguiente Diagrama representa las relaciones entre las nuevas tablas y las
viejas.
Diego pulsó <F5>. Todo parecía bien, así que reorganizó la base de datos.
Luego accedió al Wiki, documentó la terminación de esta etapa y dejó a Mike
la tarea de probar lo hecho y a Julia la tarea de aplicar los Work With al resto de
las transacciones.
128
Capítulo 5
Cuando el “qué” no alcanza
129
valores, y para cada línea (un
vuelo real) desplegada en el grid,
llamar a la transacción
FlightInstance en modo Update y
modificar los valores
manualmente, lo que seguramente
será inaceptable para el usuario
final. La otra alternativa: desde
algún web panel en variables
pedirle al usuario los valores de
fecha y hora a modificar, país y
ciudad, y capacidad, y el valor de
la nueva fecha y hora de esos
vuelos, y luego llamar mediante una acción (botón o imagen)
a un procedimiento que recorra los registros y realice el cambio – al tiempo que
lo decía, abría su notebook e implementaba el web panel.
Event Enter
ChangeDepartureTime.Call( &FlightDate, &FlightTime,
&DepartureCountry,&DepartureCity, &NewDate, &NewTime, &capacity
)
EndEvent
130
donde las variables que aparecen fueron declaradas por Diego como
parámetros de entrada del procedimiento, en el selector Rules:
39
Respire profundo, amigo lector, no se nos vaya a quedar sin aire a la mitad…
131
valores de los primeros cuatro coincidan con los de las variables recibidas por
parámetro, y el valor de FlightTotalCapacity sea mayor o igual que el de la
variable también recibida por parámetro”, cámbiale el valor que contengan
FlightInstanceDate y a FlightInstanceTime por los indicados.
- Sigo sin entender. Esos atributos están en tablas distintas.
FlightInstanceDate y FlightInstanceTime están en FLIGHTINSTANCE, sí,
pero los demás están todos en FLIGHT… ¿No deberías recorrer…?
- Cierto –interrumpió Diego sin dejarla terminar-. Pero FLIGHT está
en la tabla extendida de FLIGHTINSTANCE. Es decir, por cada vuelo real,
tienes un FlightNumber (clave foránea) que corresponde a un y solo un registro
de FLIGHT, que tiene un FlightDepartureCountryId, un
FlightDepartureCityId, y un FlightTotalCapacity. Hay una relación N a 1 entre
FLIGHTINSTANCE y FLIGHT. Con eso no necesita más: sabrá que debe
recorrer la tabla FLIGHTINSTANCE, accediendo para cada registro a su
correspondiente en FLIGHT, es decir, haciendo un join automático para saber
el valor de los atributos FlightDepartureCountryId, FlightDepartureCityId y
FlightTotalCapacity y con eso decidir si se queda o no con el registro para
procesarlo (por supuesto, siempre y cuando el propio registro de
FLIGHTINSTANCE cumpla con ser del día y hora indicados por las variables
&CurrentInstanceDate y &CurrentInstanceTime). La condición que impone
sobre un registro para quedarse con él y procesarlo, es la concatenación con
And de todas las condiciones lógicas que ves en las cláusulas Where del For
each. Como antes hablamos de tabla base, aquí también: para el For each es la
tabla que recorre. Observa, entonces, cómo conociendo las relaciones entre las
tablas y dónde están los atributos en el momento del análisis, GeneXus puede
inferir el acceso a los datos sin que le tengas que nombrar tabla alguna. Esto
tiene una consecuencia importante: permite que se puedan cambiar de tabla
esos atributos, sin que la descripción del procedimiento pierda validez40.
40
Por supuesto: siempre y cuando los atributos pertenezcan a una misma tabla extendida. En caso contrario GeneXus arrojará
un error, informando que no fue posible relacionar los atributos.
132
- ¿Por qué? Hmmm… parece un poco mágico, no me sentiría segura
programando así.
- No Mary, créeme, nada es mágico. Miremos el listado de navegación
para aclarar tus dudas (y las mías; siempre antes de ejecutar un procedimiento
para probarlo, el primer test ya está hecho: es el listado de navegación, que
informa sobre gran parte de lo que te da inseguridad. Por eso, nunca ejecuto sin
antes mirar ese listado. Bueno, está bien, a ti no te voy a mentir. A veces no lo
miro, de apurado nomás, pero luego me arrepiento). Mirémoslo juntos… (si
sólo quiere verse el listado de navegación, en lugar de hacer <F5> que además
construirá todos los programas, se puede hacer botón derecho sobre el tab del
procedimiento, y elegir la opción View Navigation)
Tabla base
- ¿Ves? Te informa la tabla base del For each, el orden por el que
procesará la información41 y el índice que propone para satisfacer ese orden (si
es que existe). Además te informa los filtros de navegación, es decir, desde
dónde empezará a recorrer la tabla y hasta dónde (en nuestro caso, recorrerá
41
El orden puede indicarse mediante la cláusula “Order” del comando For each. Como no se ha indicado, y no existe ningún
índice en la base de datos que permita optimizar la búsqueda de los registros que cumplan estas condiciones, asume clave
primaria de la tabla a recorrer.
133
toda la tabla42). Luego se observan las Constraints (filtros sobre los registros a
recorrer). Más abajo te informa que por cada registro de FLIGHTINSTANCE
deberá hacerse un join con la tabla FLIGHT de su extendida (para realizar los
filtros por FlightDepartureCountryId, FlightDepartureCityId y
FlightTotalCapacity) y que se actualizarán de la tabla FLIGHTINSTANCE los
atributos FlightInstanceDate y FlightInstanceTime. Sé franca, Mary, y dime
cuánto más te costará implementar esto mismo utilizando las herramientas que
usas. Habrías implementado tú misma este join. Lo mejor de todo, en verdad,
es el hecho de seguir hablando en alto nivel. Piensa qué pasaría en tu
implementación, que seguramente requerirá nombrar las tablas, si mueves por
ejemplo, el atributo FlightTotalCapacity de FLIGHT a FLIGHTINSTANCE.
Aquí no tendrás que tocar absolutamente nada. Simplemente volver a generar el
objeto, para que GeneXus infiera la nueva navegación y ¡listo!
- Sí, comprendo. ¿Y cómo insertas y eliminas registros de la base de
datos?
- Bueno, el comando For each te permite recorrer una tabla de la base
de datos, y acceder a su extendida, tanto para consultar solamente, como para
actualizar atributos. Por este motivo, es un comando importantísimo, que podrá
ser utilizado no sólo en procedimientos sino en todo objeto GeneXus donde se
admita código procedural (como los eventos de transacciones y web panels, por
ejemplo). Para las inserciones y eliminaciones se han creado dos comandos
específicos: New y Delete43.
42
Si existiera un índice compuesto por {FlightInstanceDate, FlightInstanceTime, FlightDepartureCountryId,
FlightDepartureCityId, FlightTotalCapacity}, lo propondría sin dudar, optimizando la consulta, de modo que ya no tendría que
recorrer toda la tabla y esto lo informaría en los “Navigation filters” del listado de Navegación. Aun si no existiera este índice,
pero sí uno compuesto por un subconjunto de esos atributos, lo propondría. Es decir, siempre que exista un índice que le
permita optimizar la consulta, lo propondrá. (No lo dijimos antes, pero en cualquier momento usted puede pedirle a GeneXus la
creación de un índice de usuario) ¿Por qué decimos “lo propondrá”? Pues para los generadores SQL si bien esta información es
útil e influyente, en realidad es el propio DBMS quien resuelve el plan de acceso más apropiado. Para los no SQL (RPG,
COBOL, VB-Access, VFP-DBF) en cambio, es vital. Existe mucho para conversar sobre este tema, estimado lector, pero no
viene a cuento en esta historia.
43
En ninguno de los casos se hacen controles de integridad referencial, permitiendo así insertar registros que apuntan a un
registro inexistente, o eliminar registros que son apuntados por otros, dejando a estos últimos huérfanos. El único control que se
realiza es el de unicidad. Hablamos de controles programáticos, ya que en GeneXus se puede indicar si se desea activar la
integridad referencial a nivel del DBMS, en cuyo caso siempre se controlará.
134
- Ahora que pienso, no estás controlando en el procedimiento lo que sí
controlas en la transacción FlightInstance: que no intenten cambiar la fecha y
hora del vuelo, adelantándolo. Si mal no recuerdo, allí tenías una regla de error
que controlaba eso.
- ¡Tienes razón! -. Pensativo, Diego rumiaba algo. Mary lo miraba
entusiasmada. Tras unos segundos de meditación, rompió el silencio-. Tendría
que hacer ese control en el procedimiento, preguntando con un comando If
antes de apresurarme a actualizar así sin más. Toda vez que quiera modificar
fecha y/u hora de un vuelo real, en cualquier objeto, tendré que volver a hacer
el mismo control que hacía mediante la regla en la transacción. Repitiendo el
código. Una y otra, y otra vez. Quizás en alguna me olvido…. o me equivoco.
135
(FlightNumber) inexistente, o pretendiendo agregar algún pasajero inexistente,
fallará y no me dejará hacer la inserción, tal como ocurre con la transacción. O,
por ejemplo, si quiero agregar un pasajero existente pero con el que se excede
la capacidad del vuelo, no me dejará realizar esa inserción, tal como sucede
cuando quiero hacerlo vía la transacción, dado que tengo una regla de error en
ella que lo impide –. Diego abrió la transacción FlightInstance y mostró a Mary
la sección Rules. Usted, estimado lector, puede buscarla en el capítulo
anterior-. Encapsular la lógica de la transacción, quedándose con la estructura
pero dejando de lado el form es posible: se llama Business Component. Es un
tipo de datos generado por GeneXus a partir de una transacción44.
- ¿Y cómo haces para encapsular todo eso? ¿y cómo lo usas?
- A la transacción que quieres utilizar de esta otra forma “silenciosa”,
alcanza con cambiarle el valor de su propiedad Business Component a “True”.
A partir del momento en que guardas este cambio, GeneXus creará un nuevo
tipo de datos Business Component, cuyo nombre será el de la transacción45.
- ¿Y cómo lo utilizas? –volvió a preguntar, intrigada.
- A través de variables basadas en ese tipo de datos. Siempre que
quieras actualizar, insertar o eliminar una
instancia de vuelo, en vez de utilizar For each,
New y Delete, podrás utilizar toda la potencia de
la transacción FlightInstance a través de las
propiedades y los métodos de una variable basada
en el tipo de datos FlightInstance, business
component, que GeneXus generó. Modifiquemos
el procedimiento anterior para hacer la
actualización mediante un Business Component –
prosiguió Diego su monólogo; Mary observaba
expectante sin hablar-. Necesitamos una variable,
&FlightInstance de este tipo de datos (generado
44
Los Business Components se obtienen de objetos transacción, pero pueden ser utilizados desde cualquier otro objeto
GeneXus, para hacer inserciones, eliminaciones y modificaciones en sus tablas.
45
Si la transacción tiene 2 niveles, creará un business component por nivel.
136
cuando pasamos a “True” la propiedad Business Component de la transacción).
En ella alojaremos todo el contenido de la instancia de vuelo a cambiar. Esta
variable será, lógicamente, estructurada, será el “buffer” donde temporalmente
tener los datos del vuelo que queremos manipular en un momento dado. El
código nos quedaría más o menos así…- y sustituyó en el objeto las dos
asignaciones directas por las cuatro sentencias que se pueden observar en la
siguiente imagen-. Observa que aquí sólo utilizamos el For each para recorrer
la tabla base y filtrar, pero no actualizamos directamente:
137
y FlightInstanceTime y luego hubieras presionado [Confirm]. ¿Y aquí?
Cambias lo valores en la variable, y luego ejecutas el método Save. Lo que
pasará como consecuencia de esas acciones, es prácticamente lo mismo.
Quiero decir, ¿qué pasaría si en la transacción modificaras fecha y hora de
forma tal que adelantaras el vuelo?
- Se dispararía el error ese que habías programado como regla, y no nos
dejaría grabar, o sea, no se realizaría ese cambio en el registro. Lo dejaría como
estaba antes.
- Exactamente. ¿Y qué piensas que sucederá aquí, en el procedimiento?
- ¿Lo… mismo? –preguntó y afirmó al mismo tiempo-.
- ¡Lo mismo! Disparará la regla, y como su condición se satisfará, no te
dejará grabar el cambio.
- ¿Y cómo me entero?
- Bueno, en el caso de la transacción, como es un objeto interactivo, allí
no hay duda, te despliega mensaje. Sin embargo, un Business Component no lo
es, razón por la cuál los posibles mensajes y errores producto del
procesamiento (ejecución de métodos Load, Save o también Delete, el utilizado
para eliminar) deben quedarte almacenados en algún lado. GeneXus los inserta
en una colección (lista) de mensajes asociados a la variable Business
Component de que se trate, y la puedes recuperar con un método
(GetMessages). Luego tienes la posibilidad de recorrer esa lista de mensajes…
Pero si sólo te interesa saber si se produjo algún error, tienes el método Fail
que te devuelve “True” en caso de error: así, en nuestro caso, programarías “if
&FlightInstance.Fail()…”
- Entiendo. ¿Y qué otros errores podrían producirse cuando manipulas
la información con el Business Component?
- Los mismos que cuando intentas insertar mediante la transacción
FlightInstance. Es decir: los de integridad referencial (si quisieras cambiar por
ejemplo el vuelo, FlightNumber, que es clave foránea, por uno inexistente), el
de unicidad (por ejemplo, si quisieras insertar una instancia de vuelo nueva, y
le asignas un FlightInstanceId que ya existe para otro registro), las validaciones
138
de tipos de datos, las reglas de error declaradas en la transacción, y cuyas
condiciones de disparo se satisficieran…Observa cómo en ese sentido es más
seguro hacer un procedimiento utilizando Business Component donde sabes
que se van a disparar todas las reglas del negocio, sin tener que repetirlas.
- ¿Todas? Si no te quedas con la interfaz visual, ¿qué ocurre si en la
transacción hubiera una regla que disparara una llamada a un web panel, por
ejemplo?
- Bueno, no todas… GeneXus ignora en el Business Component las
reglas que hacen uso de interfaces de usuario. Esa no será incluida.
- Interesante… -dijo pensativa.
139
Le tendré que decir a este procedimiento que tendrá una salida como pdf.
La idea del Layout es declarar qué es lo que quiero mostrar en la salida.
Como resultado de la ejecución, quiero mostrar para cada ciudad, todas sus
atracciones. Evidentemente, el usuario final tendrá que tener Acrobat
Reader instalado para que desde el Browser se pueda abrir correctamente el
archivo pdf generado. Para indicar qué es lo que se quiere mostrar en la
salida y su formato, el Layout contiene áreas de impresión, que son
controles llamados printblock. Coloreados en la imagen, puedes ver donde
empiezan dos de esos controles: City_Block y Attraction_Block. Esos
nombres se los di yo luego de insertar cada control. Dentro de cada
printblock insertas la información (atributos, variables, imágenes, líneas,
recuadros, etc.) que deseas desplegar en esa área de datos, cuando sea
invocada desde el código. Por ejemplo, observa que dentro de City_Block
he insertado los controles atributo CityId y CityName, porque esa es la
información de cada ciudad que querré mostrar en el listado.
- ¿Y sólo con eso GeneXus ya sabe qué listar y cómo?
- Mary, espera. El amigo no es mago, sólo es inteligente. Con eso solo
no puede saber cómo quieres listar la información. Falta el código –dijo
enarcando las cejas, con expresión risueña, viendo aparecer un rubor...
140
- Bueno, lo presentas como tan maravilloso… bien podría adivinar tus
intenciones, ¿no? –sonrieron ambos.
- ¿Tú las adivinas? –avanzó Diego.
- ¿Y si las explicitas? –contestó Mary sin retroceder, mirándolo
fijamente a los ojos, con una expresión que a él se le antojó encantadora.
- ¡Sus deseos son órdenes!...–. Y tras un breve silencio que a ambos
pareció eterno, él continuó-. Quiero recorrer las ciudades, y para cada ciudad,
sus atracciones. Esto implica la utilización de dos For eachs anidados.
Mary aproximó su silla a la de él, tomó el mouse sin preguntar, siempre
sonriendo, y se posicionó en el selector Source. La ventana se transformó,
mostrando el siguiente código:
Diego continuó:
- Observa que no necesitas establecer el filtro por ciudad para el
segundo For each: es que existe una relación 1-N directa entre las tablas que se
recorren, siendo uno de los casos más comunes de For eachs anidados. La tabla
base del For each externo es COUNTRYCITY, y la del interno es
ATTRACTION (por los atributos de los printblocks involucrados), y como
GeneXus conoce las relaciones entre las tablas, él implícitamente aplica la
141
restricción sobre los registros a recuperar. Pero es más potente que eso,
encuentra también relaciones 1-N indirectas. Por ejemplo, si quisieras listar
para cada aerolínea, la cantidad de instancias de vuelo46.
Mary, muda, no emitía palabra. Miraba la pantalla. Una sonrisa seguía
estampada en su rostro. Posicionó el mouse sobre el Tab del procedimiento
donde se encontraba su nombre, y con botón derecho seleccionó “View
Navigation” del menú contextual. Diego la miraba. Apareció un nuevo Tab en
la pantalla, conteniendo la siguiente ventana:
142
ejecute en el Browser. Además deberás incluir una regla en el selector Rules
“output_file("AttractionsPerCity", "pdf");” que es donde le dices que
la salida será un archivo pdf, con el nombre que especifiques. También puedes
decirle que quieres que envíe la salida a alguna impresora de las que estén bajo
el alcance de la computadora desde la que se esté ejecutando el navegador con
la aplicación.
Para ejecutarlo, Diego se posicionó sobre el tab con el nombre del
procedimiento, y con botón derecho seleccionó la opción “Run with this only”,
para que GeneXus construya y ejecute ese procedimiento únicamente. La cara
de asco de Mary no se hizo esperar. Es que el listado sólo mostraba datos, sin
ningún formato. Diego no esperó. Grabó el procedimiento con otro nombre, y
le mejoró el diseño y la información mostrada…
143
Por fin Mary rompió el silencio.
- Me atrae París… ¿Por qué no vienes esta noche a cenar a casa? Y me
sigues contando…
144
¿Y las consultas?
Otro de los grandes alcances de GeneXus X es el objeto Query que permite
crear consultas a la base de datos, y mejorar la salida de la información
recuperada. Ésta puede ser visualizada en diferentes formatos (barras, tortas,
3D o planas, área, etc.) lo que ayudará a enriquecer sus aplicaciones. Además
permitirá al usuario realizar consultas dinámicas (qué measure ver) a través de
pivot tables, etc.
Una consulta de este tipo es definida como una estructura. Supongamos que
deseamos obtener una consulta de cantidad de despegues en una fecha dada
(FlightInstanceDate) por aerolínea (AirlineName) para cada ciudad
(CityName); en la siguiente imagen se resume esta intención:
145
declarado la función Count, pero según las necesidades, se pueden declarar
también a Sum y Average47
Podemos también incluir parámetros, y en este caso en particular hemos
incluido una variable basada en un atributo en particular.
47
No todas las funciones de agregación aplican para todos los tipos de atributos. Sum y Average están solo disponibles para
atributos numéricos.
146
Luego accedemos a sus propiedades y seteamos aquellas que sean
necesarias. En este caso se ha
modificado la propiedad Query, a la
cual se le asignó el nombre del objeto
Query que definimos al inicio,
QSoldSeatsByArrivalCities, y también
se ha modificado la propiedad Type;
esta tiene por finalidad establecer alguno
de los tres tipos posibles de salidas:
Pivot Table, Table, y Chart. Le
recomendamos que pruebe para ver las
diferencias entre ellos. Considere que
para el valor Chart dispondrá además de
la propiedad ChartType donde podrá escoger un estilo entre doce posibles. Y
eso es todo para lograr una construcción básica de un Query.
En el selector SQL statement se puede observar el código que GeneXus ha
generado y que será ejecutado sobre la base de datos para obtener la vista
deseada en cuanto se ejecute el web panel diseñado para tal fin.
147
Ahora <F5> y, ¡voilà!
148
Capítulo 6
Más declaraciones
Nuevos protagonistas del conocimiento: Data Providers
La meta de GeneXus: que usted declare, declare y declare. Cuando uno
declara dice el qué, y no se preocupa por el cómo, pues éste varía con el
tiempo. Con frecuencia se encuentran nuevas formas de implementación,
mejores, más eficientes. El qué es estable, la esencia. Conocimiento que
permite automatizar. Las transacciones y los business components eran, así, las
estrellas de GeneXus: declaraban la lógica del negocio. Declaraban
conocimiento. GeneXus encontraba, en cada etapa de su evolución, un cómo.
Ahora, un nuevo protagonista irrumpe en escena: el Data Provider. Para una
gran variedad de casos, pasaremos de programar procedimientos, a declarar
data providers.
¿Quedó usted intrigado? Mary también. Vea lo que pasó.
¿Declarándose?
Sin responder a la pregunta que ella dejara sonando en el aire, Diego abre
torpemente su inbox de correo y encuentra un e-mail de la agencia donde le
solicitaban un nuevo requerimiento: debía generar un archivo de interfase con
formato XML para enviársela a cierta aerolínea donde figurara la cantidad de
plazas vendidas por vuelo de esa aerolínea, en cierta fecha.
- Mira esto –susurró a Mary girando la notebook para que ella leyera.
- Fácil –contestó ella demorando en leer. Su vista se había detenido
antes en otros mensajes de correo, y Diego lo percibió, pero no dijo nada–.
Implementas un procedimiento parecido al que vimos. Pero ¿cómo se hace en
GeneXus para generar la salida en formato XML? –si bien parecía estar
149
pensando en otra cosa, su conversación era completamente coherente. ¿En qué
pensaría sin decirlo?
- No es complicado, ya veremos. ¿Pero sabes una cosa? Esta vez no lo
haremos con un procedimiento. Antes de la X no nos quedaba otra opción, pero
ahora disponemos de algo más potente para este tipo de casos.
- ¿Más potente?
- Sí, nada de codificación procedural. Fíjate que aquí necesitas
consignar información estructurada: imagínate cómo debería ser el XML
resultante, por ejemplo… –. Creó un documento xml, escribió y mostró:
150
- ¿Ves cómo en este código están mezclados los elementos de la
entrada, con los elementos de la salida? Dime la verdad, sin importar si
entiendes o no el código, ¿no te resulta complicado aquí encontrar cuál será la
estructura resultante y de qué modo se carga? Esto es porque los
procedimientos privilegian el lenguaje de transformación del input para
obtener el output. Por lo que el output queda oscurecido y no es fácil
identificarlo. Sin embargo, en los Data Providers lo privilegiado será el Output.
De hecho, declaras tu intención, mediante simples ecuaciones del estilo x = y
para cada elemento de la estructura jerárquica que quieres devolver cargada.
- Muy interesante, Diego, pero se me hace un poco tarde, y veo que esto
puede llevar un tiempo…
- ¡Cinco minutos! – acotó inmediatamente, ocupado en demorar el fin
del encuentro. Mary dudó unos segundos y por fin continuó:
- Bueno, entonces anda, “declara”.
Diego creó un nuevo objeto, al que llamó LoadNumberOfPassengers, de tipo
Data Provider. Mary observó la imagen.
151
- Ajá. Tiene casi los mismos selectores que los procedimientos.
- Sí, lo que varía es lo que se escribe en el selector Source, pues no será
código procedural, sino declarativo. Pero vamos por partes. Según el
requerimiento, algún otro objeto capturará del usuario el número de aerolínea y
la fecha, y a ambos los recibiremos como parámetros. Así que definamos
entonces las variables y declarémoslas en las Rules –tras lo cual lo hizo.
parm(in:&AirlineId, in:&DepartureDate);
152
- ¿Ves? –se apresuró a proseguir-. Este tipo de datos se compone de
tres miembros, de nombres Name, DepartureDate y Flight. Los dos primeros,
de tipos de datos simples, y el tercero, será una colección de ítems (observa el
check box Is Collection), cada uno de los cuales es estructurado; les hemos
llamado FlightItem. Los miembros de éste último estructurado también son
tres. Observa que como he llamado a los dos primeros miembros igual que
atributos de mi KB, infirió como su tipo de datos el mismo que el de los
atributos homónimos. Les puse el mismo nombre que a los atributos porque en
realidad voy a cargar este SDT con los valores de esos atributos, luego. Pero no
nos detengamos en ello.
Ante la atenta mirada de Mary, Diego picó sobre el nombre del SDT que
ahora aparecía en el árbol del Folder View, lo arrastró hacia el área Source del
Data Provider y soltó el botón, tras lo cual apareció:
153
De forma automática, GeneXus inicializó
en el Source la estructura que será devuelta
por el Data Provider; es más: dio valor a la
propiedad Output que indica cuál será la
estructura devuelta por el Data Provider.
Ahora Diego sólo debe sustituir los
comentarios del Source por los valores que
correspondan a cada miembro.
154
Vio cómo los rojos labios de Mary se unían para pronunciar lo que adivinó
ser un “pero…”, aunque no llegó a saberlo, pues continuó:
155
- Efectivamente. En el lenguaje de Data Providers tendrás grupos, estos
son los que contienen elementos. En nuestro caso: SeatsSoldByFlight, Flight y
FlightItem son grupos. Los grupos contienen elementos, variables y/u otros
grupos. En cualquier caso, un grupo podrá presentarse en la salida una sola vez,
o múltiples veces. GeneXus tiene la inteligencia para determinarlo.
- ¿Cómo?
- Por lo que decías tú. La presencia de atributos a la derecha de las
asignaciones en los elementos, así como en las cláusulas where del grupo están
determinando que esos grupos son como for eachs, que acceden a una tabla
base.
- Si no hubiera cláusulas where, tal como estaba el Source antes de que
las agregaras, y si en lugar de atributos a la derecha de las asignaciones tuvieras
variables o literales…
- …en ese caso, en la salida tendrías cargado el SDT de forma tal que la
colección Flight estaría compuesta únicamente por un ítem. En cambio, en
nuestro caso, el grupo Flight contendrá muchos ítems, tantos como instancias
de vuelos haya para esa fecha y aerolínea, y en cambio el grupo raíz
SeatsSoldByFlight será único, puesto que estamos filtrando por AirlineId.
- Ah, entiendo –exclamó Mary–. Es como si tuvieras un par de For
Each anidados. El primero, por los atributos que aparecen, recorrería la tabla
AIRLINE, y el segundo la tabla FLIGHTINSTANCE.
- Perfectísimamente. Mira el listado de navegación:
156
- Observa además cómo en el lado derecho de los elementos puedes
poner tanto atributos como cualquier función o fórmula que devuelva un
resultado del mismo tipo de datos que el que definiste para ese elemento en el
SDT. Por eso utilizamos una fórmula count para SoldSeats. En realidad, como
esa fórmula la tenemos también global en el atributo
FlightNumberOfPassengers, podríamos haber colocado directamente este
atributo. No lo hice para mostrarte que es posible escribir cualquier cálculo.
- Entiendo ahora por qué dices que los Data Providers son declarativos.
Lo que tienes que hacer es declarar cuál será la estructura resultante, y luego
cargarla a través de ecuaciones x = y donde en y declaras cómo se calcula x.
Ahora, ¿cómo devuelves el XML que necesitas?
- Bueno, en realidad deberás invocar a este Data Provider como si fuera
un procedimiento común y corriente desde cualquier objeto, y hacer lo que
quieras con la salida, por ejemplo, convertirla a XML –contestó Diego-. Creo
que lo mejor será que lo haga, así lo vas a entender mejor.
157
Creó un Web Panel y tres variables: &AirlineId, &DepartureDate y
&SeatsSoldXML, esta última de tipo de datos LongVarChar y de 2 MB (será la
que contendrá la información en formato xml):
158
- Cierto. El valor “true” del parámetro solamente está indicando que le
agregue el header al xml; ahora no importa… entonces luego de ejecutado el
método, la información está contenida en memoria dentro de esa variable
&SeatsSold.
- Ahora lo único que te queda es generar, por ejemplo, un archivo con
ese contenido y listo –agregó Mary mirando su reloj pulsera.
- Sí, y todavía quizá mejor: puedo enviarlo directamente por mail a la
compañía aérea.
- Ajá… Diego, escúchame, no sigas. Te agradezco la explicación, no te
enfades conmigo, pero me esperan, debo marcharme. Te espero esta noche en
casa, y seguimos con las declaraciones… ¿te parece?
- Sí, pero omití decirte que un Data Provider también puede cargar y
devolver un business component, porque la estructura de un business
component es como la de un SDT. Luego puede ser insertado en la base de
datos desde el objeto llamador, con el método Save, como siempre.
- Me lo cuentas luego. Nos vemos esta noche, ¿sí?
Diego permaneció en el bar varios minutos más, con la vista perdida por
donde ella había partido minutos antes, dejando tras de sí su estela de perfume.
159
160
Capítulo 7
Soplan nuevos vientos
Poniendo la casa en orden: categorización
161
subcategorías. Pero simultáneamente, podemos querer categorizar el mismo
universo, de acuerdo a otros criterios. Lo que quiero decir: puedes incluir un
mismo objeto en varias categorías distintas. Por ejemplo, suponte que quieres
organizar además por sistema, subsistema, etc. Nada impide que un objeto esté
en la categoría correspondiente a un subsistema, y además en la “Under
Review” de la “Status”. O puedes categorizar los objetos según si se utilizarán
en el Back-end de la aplicación, o en el Front-end. Las categorías nos permiten
organizar y visualizar la información de nuestra KB de acuerdo con nuestras
necesidades puntuales.
- Multicategorizar…–. A ella le brillaban los ojos. Al menos eso le
pareció a él.
- Cuando seleccionas un objeto del Folder
View, debajo de la ventana de propiedades, tienes
un contenedor rotulado Categories que comienza
con una pequeña toolbar en donde puedes tanto
agregarle categorías al objeto como eliminárselas.
Pero, como siempre, tienes varios caminos para
llegar a Roma…
- Hoy estuviste categorizando… eso
significa que tuviste que ir uno por uno a todos
los objetos y asignarles las categorías
correspondientes, ¿no? ¡Mucho trabajo!
- Sí –contestó un poco avergonzado-. Es
que en esta versión aparecieron las categorías, y
no me he acostumbrado a usarlas, si bien fue
decisión de la empresa el uso de categorías de estado. A veces es difícil
cambiar las costumbres. Tendríamos que haber creado estas categorías desde el
vamos, y los objetos ya con la categoría “Under Construction” y luego ir
moviéndolos de categoría, en el cambio de estado. Hoy tuve que poner la casa
en orden, ya eran muchos los objetos de la KB. Pero sólo tuve que hacerlo para
162
las subcategorías de “Status” porque la categoría “To-Do Diego” es dinámica,
se mantiene sola.
- ¿Sola? ¿Cómo es eso? –. Mary estiró el brazo hasta la botella de vino.
- En este caso, aprovechando que tenía que poner la casa en orden, puse
comentarios que contenían el texto “To-Do Diego” en algunos objetos que
tenía pendientes de cerrar; también otros compañeros lo habían hecho. Luego
creé una categoría dinámica. Las categorías dinámicas tienen asociada una
condición de búsqueda (que podrá ser por palabras o por propiedades, como ya
te mostraré). Todos los objetos resultantes de la búsqueda, pasan a formar parte
de la categoría. Puse en la condición de búsqueda que tuviera el texto “To-Do
Diego” y allí aparecieron todas las cosas que tengo por hacer, varias por
cierto…
Veamos cómo lo hizo…
163
El motor full-text search exploró la KB y reportó todos los lugares donde
estaba presente. Obsérvese además cómo desde esta consulta puede crearse la
categoría dinámica de igual nombre, “To-Do Diego”, que Diego mostró antes a
Mary. Para ello alcanza con hacer [Save as Category]. Tal vez sea lo que hizo
Diego ese día…
Note que debajo se propone también una búsqueda, pero en este caso sólo
de propiedades. Por ejemplo, si se quisieran ubicar todos los objetos de tipo
procedimiento, agregaríamos una propiedad:
164
Base, rimel, sombras… Maquillando la aplicación
Esa misma tarde, unas cuantas horas antes, Julia terminaba la
documentación en la KB cuando Mike, que finalizaba los últimos testings,
comentó:
- Me enteré por Diego que quieren cambiar el estilo de las páginas. Él
estaba muy atareado, terminando el desarrollo de unos objetos, y me derivó este
tema a mí. Me dijo que hablara contigo para pedirte los detalles.
- Sí, me enviaron un e-mail que describe más o menos el aspecto que
desean y lo copié como Talk del Main Document; luego lo documento bien.
Dejaron a nuestro gusto la composición de colores y demás. Yo ya había
ingresado hace tiempo el logo de ellos a la KB, en el nodo Images. Quieren que
quede situado arriba, en todas las páginas.
- Eso lo hago ahora mismo. Sólo tengo que abrir la master page
AppMasterPage creada con la KB y utilizada por defecto por todos los objetos.
Es donde se encuentra centralizado el encabezado y pie que tendrán todas las
páginas del sitio; luego sustituyo el encabezado “Application Header” que sale
por defecto, y listo. Para todo
lo otro que piden, llamemos a
Paul, el diseñador gráfico…
- Pero Paul no sabe nada de GeneXus, ¡¿y tú le vas a permitir que abra
los objetos y modifique los controles en los forms, cambiándoles el color, el
tamaño, y demás?!
Mike no pudo reprimir la carcajada.
- No Julia, cada uno en lo suyo. ¡Ni loco le dejo tocar nuestros objetos!
Pero necesito que configure para cada control de los forms, su diseño. Por
ejemplo, necesito que especifique para los botones de las transacciones su
tamaño, color de fondo, tipo de letra, etc. Lo mismo para los grids y demás
controles. Quisiera centralizar eso y que él no tenga que tocar los controles
“en” los objetos. Para eso es que existe el objeto de tipo Theme. Observa que
bajo el nodo Themes bajo el Customer del Folder View, aparece uno de nombre
GeneXusX. Es el que asoció GeneXus por defecto a nuestros objetos. Si lo
165
abro, verás que se trata de una lista, en árbol, de clases de controles (aunque no
sólo).
48
No habíamos hablado hasta el momento de exportación/importación en GeneXus. En este libro solamente diremos que se
pueden exportar objetos GeneXus y otro tipo de conocimiento de una KB en archivos de extensión xpz, que luego pueden ser
importados en otra o la misma KB. Para ello está la opción del menú Knowledge Manager.
166
lo importaré y ¡listo! ¡Diseño aplicado! Paul no necesita tener GeneXus, sino
solamente el Theme Editor, un utilitario que sólo permite trabajar con estos
objetos. Es específico para el diseñador gráfico. Si no, también lo puedes hacer
desde dentro de GeneXus.
“De hecho, ahora que veo, el logo de la empresa está en la línea de los
naranjas. Así que mejor utilicemos el Theme de nombre
Orange (para ello lo marcamos en el check box) y
hagámosle un Save as… TravelAgencyTheme. Así
usamos por entero uno propio. Enviémosle este a Paul.
Para probar y mostrarte, mira, modifiquemos los
colores de algunas clases del theme –tras lo cual, con
el nuevo theme TravelAgencyTheme abierto, se puso a
cambiar las propiedades de las clases Grid,
FreestyleGrid y Table, y grabó-. Ahora lo que tengo
que hacer es decirle a la KB49 que utilice en todos los objetos ese tema:”
49
Estrictamente hablando a la versión de la KB que esté activa. Sobre versionado de KB, busque próximos episodios o en las
distintas fuentes de documentación de GeneXus.
167
tome en cuenta que los objetos tendrán ahora este theme, observe cómo luce en
ejecución. Por ejemplo, veamos el Work With Airline, y luego la transacción
para modificar una aerolínea:
Los tipos de objeto Master Page y Theme son los que más importan a la hora de
diseñar la estética de la aplicación.
168
…se observa, a la izquierda, el contenido del panel Latest Changes View y a la
derecha la historia de la transacción Airline. Note que existieron seis
modificaciones desde su fecha de creación, por lo tanto hay siete números de
versión.
También podrá comparar dos versiones entre sí y obtener sus diferencias.
Por ejemplo, puede seleccionar dos versiones y luego, mediante botón derecho
del mouse, pulsar
sobre “Compare
Selected Revisions”.
Como resultado de
comparar las
revisiones 12 versus
14 de la transacción Country, GeneXus mostrará las diferencias en dos
ventanas adyacentes, como se muestra a continuación para el ejemplo (en la
versión 12 no habían reglas definidas).
169
¿Versionado de la KB?
El tiempo hacía lo único que sabe hacer; Mary y Diego parecían no percatarse
de ese avance inexorable.
- Diego…
- ¿Sí?
- ¿No crees que llegó el momento de… –se alisó la falda con las manos,
y carraspeó antes de continuar, segundos eternos para él-…hacer una copia de
la KB?
- ¡Ah, copias de respaldo! Bueno, en realidad en GeneXus lo puedes
hacer muy muy muy fácilmente. De hecho, dentro de la misma KB puedes
contener “fotografías” de diversos estados de la misma en el tiempo. Y no solo
eso, puedes hacer desarrollos paralelos de distintas versiones. Por ejemplo,
quieres poner en producción una versión, y continuar desarrollando nuevos
requerimientos, de tal manera que puedes ir luego haciendo correcciones a la
versión de producción a medida que el usuario lo va requiriendo, que no
estorben el desarrollo principal.
- Interesante, ¿me lo cuentas luego? –. Se levantó del sofá y parada
delante de él le ofreció extendida la palma de su mano-. ¿Vienes?
170
Situaciones que se repiten: para no decir siempre lo mismo,
Data Selectors
Es común encontrarnos codificando las mismas “situaciones” (filtros y
órdenes de acceso a la base de datos) en distintos lugares de nuestra aplicación
donde recuperamos datos, desperdigando por variados lugares la misma lógica
(o parte de ella).
Observando los siguientes trozos de código:
Pero tiene más ventajas. Es muy común que la gente incorporada al staff de
desarrollo en un momento dado desconozca los criterios o filtros comunes de
171
una aplicación. Suponiendo que se quiere promocionar fuertemente el turismo
africano, suele pasar, por ejemplo, que se le pida un listado de ciudades y
salgan todas las ciudades en lugar de sólo las africanas. La centralización de
estos criterios reduce enormemente la curva de aprendizaje haciendo
productivo al personal rápidamente y reduciendo el número de errores.
En el ejemplo anterior, si le diéramos un nombre a esas cláusulas comunes
como si fuera una macro que luego pudiera expandirse… ¡obtendríamos un
Data Selector! Un nombre para un criterio de búsqueda de datos. En nuestro
caso: AfricanCities.
172
A la derecha del comando For each se ha escrito la cláusula Using la cual
dice que “se utilice el contenido” del Data Selector AfricanCities; y eso es
todo… si luego modificamos el criterio, lo hacemos dentro del objeto Data
Selector y ¡se cambia en todos lados automáticamente!
173
La reserva en sí misma, expresada por el primer bloque del esquema, no es
ni más ni menos que la puesta en ejecución de la transacción a la que
representa.
Luego se evalúa determinada condición, como podría ser que los datos
están correctos y completos. A los efectos de exponer un ejemplo, vea las
siguientes condiciones:
174
aerolíneas, países, ciudades, vuelos, pasajeros, etc., sólo podrá ser efectuado
por empleados de la empresa, con permisos, a través de la intranet), con otros
que pueden utilizarse en el front-end, es decir, en el sitio Web que será
consultado por personas de todo el mundo vía Internet.
Sin embargo, Diego, Mike y Julia han trajinado mucho más de lo que
hemos podido apreciar. Han hecho la página principal de la Intranet,
modificando un poco la página Home creada por el pattern Work With. Han
agregado una sección de login a la master page… web panels con consultas
variadas y distintos menús.
Esa misma tarde, sin ir más lejos, antes de la cita con Mary, mientras Paul
recibía un theme para modificar, Julia se contactaba con los usuarios de Travel
Agency y Mike terminaba los testeos. Diego observaba detenidamente cómo
había quedado la página principal del sitio web de la empresa:
175
Obsérvese que se brinda información de hoteles, de rentadoras de autos y
del estado del clima. Estas son aplicaciones de terceros, publicadas como Web
Services que se pueden integrar (consumir) fácilmente dentro de GeneXus, con
el nuevo tipo de objeto External Object. A esta altura el lector tiene todos los
elementos para hacer la radiografía de la página y sospechar qué puede haber
detrás de la misma: un web panel, web components, master page, theme,
imágenes, contoles…
Final… ¿o principio?
176
177