martes, 30 de junio de 2009

Valor "Empty" para atributos con dominio en GeneXus

Supongamos que tengo un dominio ESTADO, definido como Char(3), que puede tener valores {'ING', 'ABI', 'CER'} (ingresado, abierto, cerrado).

Si fuera una KB nueva posiblemente usaría un dominio enumerado con {ingresado=0, abierto=1, cerrado=2}. Pero no es el caso, y además no siempre es posible elegir los valores que va a tener un dominio, puede depender de otra aplicación o ser una base de datos que ya tiene valores.

El problema que tengo, es que si defino una variable o un atributo basados en ese dominio, el valor "empty" (como en IsEmpty() o en SetEmpty()) es '', que no es un valor válido para el dominio.

Estaría bueno poder definir cual es el valor "empty" de un dominio... Este valor además sería el "initial value" por defecto para los campos nuevos basados en dicho dominio, y sería el valor con el que se graban los registros nuevos cuando no se referencia el atributo (y se tiene la propiedad "initialize not referenced attributes" en "yes").

lunes, 29 de junio de 2009

Tecnología de votación

En el día de ayer, en Uruguay tuvimos elecciones internas, donde cada partido político decide cual será el candidato a presidente en las próximas elecciones de octubre, así como la representación de cada sector dentro de cada partido.

En estas elecciones había 17 candidatos a la presidencia en 8 partidos, y se presentaron unas 2.500 listas. Listas de papel, de las cuales la mayoría terminan en la basura. De algunas dicen que se imprimieron 2 millones...

La votación se hace por circuito electoral, según el número de credencial de la persona. Creo que habían unos 6.000 circuitos habilitados para estas elecciones. Cada circuito tiene tres personas que integran la mesa, más un policía de custodia.

La votación se realiza entre las 8:00 y las 19:30, y una vez terminada se abre la urna, se cuentan los votos, y se llevan al lugar designado por la Corte Electoral (en Montevideo el Estadio Centenario).

Esa es una explicación bastante resumida de como funcionan las elecciones, a lo que quiero llegar, es que me resulta demasiado complejo, demasiado burocrático. La maquinaria que es necesario movilizar para hacer una elección es demasiado pesada.

Hoy en día, con la tecnología que tenemos, ¿no se podría hacer de forma mucho más simple? Capaz que lo que sigue puede resultar disparatado para algunos, pero yo no lo veo tan difícil de implementar.

Una opción sería hacer la votación por SMS... Sí, por SMS. ¿Por qué no? Que cada uno registre un número de celular ante la Corte Electoral, y que vote desde ese número. Para que el voto sea secreto, al momento que llega el mensaje a la corte se debería separar la persona que lo envía (que tiene que quedar registro que votó para que no vote dos veces) del voto en sí. (*)

Otra opción sería tener terminales de votación en lugares de fácil acceso. Se me ocurre farmacias, locales de cobranza (Abitab, Red Pagos, etc.) o cajeros automáticos, que seguro hay en todas las ciudades del país. Habría que complementarlo con mesas de votación en las zonas rurales. La votación no sería un domingo como es ahora, sino que podría ser un día entre semana (o varios días) en el horario que están abiertos habitualmente estos locales. La Corte Electoral tendría que cambiar todas las credenciales por algo que tuviera una banda magnética o un chip con la información del votante.

Puede haber otras opciones...

Me parece que cualquier cosa que agilice el proceso de votación sería bueno. Gracias a que se evitaría toda la complejidad de la infraestructura de votación, se podría hacer algo mucho más dinámico. Se podrían hacer consultas a la ciudadanía sobre ciertos temas, con bastante frecuencia sin que eso genere gastos adicionales. Pasaríamos a tener un modelo de democracia mucho más directa.

(*) Calculo que no sería algo así como "voto pepe", "voto qki" o "voto pedro", sería algo más formal...

jueves, 25 de junio de 2009

Objetos que devuelven más de un valor en GeneXus

En GeneXus no tenemos el concepto de función como tenemos en Java o en C#, donde tiene que haber un tipo de datos que es el que devuelve, como en:
private int foo() { return 1; }
Por el contrario, en GeneXus los parámetros de un objeto pueden ser de entrada, salida o entrada/salida, en cualquier cantidad y orden. (*)

Por ejemplo, podría tener una función que dado un producto me devuelve el saldo en cantidad y en importe, de dicho producto en el depósito. (**)

La regla parm sería por ejemplo:
parm(in:ProductoId, out:&SaldoCantidad, out:&SaldoImporte);
Pero entonces, ¿cómo invoco ese programa?. Hay varias opciones, puede ser con call, o incluso con udp...
call(PGetSaldoProducto, ProductoId, &SaldoCantidad, &SaldoImporte)
o
&SaldoImporte = udp(PGetSaldoProducto, ProductoId, &SaldoCantidad)
En una nota hace un tiempo, hablando sobre la legibilidad del código, comentaba sobre la ventaja de usar udp en vez de call... Pero en este caso las dos opciones son malas.

Creo que sería bueno tener una sintaxis como tiene por ejemplo Python para asignar listas de valores... En Python una lista separada por comas es una tupla, que se puede devolver en una función o asignar. Por ejemplo:
# defino la funcion
def foo():
    a = 1
    b = 2
    c = 3
    return a,b,c

# la invoco
d, e, f = foo()   # resultado: d=1, e=2, f=3
En GeneXus, si tuvieramos la posibilidad de usar esta notación, podríamos escribir:
&SaldoImporte, &SaldoCantidad = udp(PGetSaldoProducto, ProductoId)
lo que deja bien claro que la intención de la invocación es cargar el valor de las dos variables de la izquierda...

---------------------------------------
(*) Si bien los parámetros pueden tener cualquier orden, creo que es altamente recomendable dejar los parámetros de salida al final.

(**) Los motivos para tener una función que devuelve más de un valor pueden ser muchos y muy variados. En este caso también se podrían hacer dos funciones, con lógica muy parecida, pero que devolviera una la cantidad y otra el importe. La ventaja de tenerlo así es que se optimiza la performance, porque hay que hacer la recorrida en la base de datos una sola vez.

martes, 23 de junio de 2009

Formato de strings: función Format vs concatenación

Algo que hago con bastante frecuencia, es armar textos que se componen de una parte fija y otra que toma valores de variables o atributos. Por ejemplo, cuando se quiere mostrar un mensaje.

En GeneXus hay por lo menos dos opciones para generar estos textos:
  1. usando la concatenación de strings con el operador +
  2. usando la función Format, que creo que se agregó en GeneXus 9.0
  3. hay una tercer forma que descarto de entrada, que es con la función Concat...
Pero entonces, ¿cuál es la mejor opción?

Con respecto a la legibilidad del código, creo que ambas opciones son buenas, puede haber algún caso que una de las dos quede mejor que la otra.

La ventaja que tiene la función Format es cuando tenemos que traducir la aplicación usando el objeto Language, porque queda todo el texto en un solo item del objeto.

Por ejemplo, el texto "El pedido número NNN fue confirmado", usando la concatenacion de strings queda
&mensaje = 'El pedido número ' + PedidoId.ToString() + ' fue confirmado'
con lo que al traducirlo tenemos que traducir los dos textos por separado:  'El pedido número ' y ' fue confirmado'...

Si usamos la función Format, nos queda
&mensaje = Format('El pedido número %1 fue confirmado', PedidoId.ToString())
con lo cual tenemos un solo literal.

Puede haber algún caso donde tal vez no se pueda usar la función Format, por ejemplo en una regla error() en una transacción, pero en todos casos donde se pueda, creo que conviene usar la fución Format...

viernes, 12 de junio de 2009

When duplicate y campos con índice unique

Si en GeneXus (9.0) se tiene una tabla con un índice unique y se hace un for each que actualiza el campo del índice, al especificar da un warning que dice:
spc0070: No When duplicate code found to handle possible duplicate condition when updating [Atributo]
La documentación del comando for each en el Wiki, dice lo siguiente con respecto al when duplicate:
When duplicate: This clause only makes sense in procedures (because it has to do with updates); it is analyzed below. This clause will be executed if, within the body of the For Each (code1), you try to update an attribute that is a candidate key (it has a unique index) and a record with that value already exists. GeneXus uses the unique index to ensure the uniqueness of that candidate key and in case it finds duplicates, if this clause is programmed in the For Each command, it will execute its code: code2. If the clause is not included, and you try to update an attribute that is a candidate key and a record with that value already exists, no code will be executed.
El problema con esto, es que en un mismo for each puedo estar actualizando más de un campo que tenga índice unique, con lo cual no se cual es el campo donde dio el error.

El código para manejar este error, entonces debería primero hacer otro for each filtrando por cada uno de los campos con índice unique para ver cual es el que falló, y luego hacer nuevamente el update de todos los demás campos. La lógica puede quedar bastante complicada...

Una alternativa a esto que se me ocurre, es que el when duplicate pueda tener una lista de atributos que componen un índice unique, y que cuando se trata de grabar una tupla y da clave duplicada, vaya al when duplicate que corresponda.

Es más, GeneXus podría ser más inteligente, y que cada when duplicate sea un manejador de errores, que se llame para cambiar los valores de los atributos involucrados en ese when duplicate antes de hacer el update...

Capaz que la idea no quedó muy clara, voy a poner un ejemplo...

Tengo una tabla con campos:
(A*
 B
 C
 D)
que tiene índices unique por {B} y {C,D}.

Podría tener el siguiente comando for each:
for each
    where A = &A

    B = &B
    C = &C
    D = &D
when duplicate B
    B += 1
when duplicate C, D
    C = &D
    D = &C
endfor
¿Qué haría este código? Intenta hacer el update con valores [&B, &C, &D]. Si no puede porque B está repetido, entonces va al when duplicate B, calcula el nuevo valor de B, e intenta hacer el update con [&B+1, &C, &D]. Así hasta que no falle más por B... Si además falla porque los campos C, D están repetidos, entonces va al when duplicate C, D, e intenta hacer el update con [&B, &D, &C].

Hoy no hay una forma medianamente elegante de hacer esto. Porque en el when duplicate primero tengo que identificar que campo fue el que dio el error, y después volver a hacer el nuevo update con los nuevos valores, por lo que quedaría una especie de actualización recursiva.

miércoles, 3 de junio de 2009

No hay peor comentario que el comentario que está equivocado

¿Qué tiene mal este código?
for each // Me fijo cantidad de la entrada
where MstId = &MstId2
    if MstTpo = 'R'
        &MstCntR = MstCnt
    else
        &MstCntS = MstCnt
    endif
endfor
Para el que lo ve así, a simple vista, no tiene nada mal. Los que leyeron el título de la nota pueden tener alguna pista.

¡Lo que está mal es el comentario! Porque &MstId2 no es una ENTRADA, es una SALIDA. Claro que el que no conoce el contexto no tiene forma de saberlo, y ahí está el peligro.

Yo estoy a favor de poner comentarios que expliquen lo que hace el programa, pero los comentarios tienen que estar bien, si no, es peor tener comentarios que no tenerlos.

Porque alguien podría leer el for each y quedarse con la idea que toma la cantidad de la entrada, cuando en realidad se queda con la de la salida.

En conclusión, más vale no tener comentarios que tener comentarios equivocados...

martes, 2 de junio de 2009

GeneXus Server

Ayer Gustavo Carriquiry publicó en su blog algunas fotos de pantalla del nuevo GeneXus Server.

Hoy ya está on-line para probarlo en http://gxserver.genexusx.com/genexusserver.

La verdad que está muy bueno. Se puede navegar por la KB (aunque por ahora no se puede editar) y ver los objetos y sus propiedades.

No se si está basado en el Citrus Proyect, pero parecería como que sí.

Es bueno ver como una idea que había propuesto en el 2006 en este blog parece ir tomando forma :)

Actualización (3/5/09 8:54): Gastón Milano publicó una nota en su blog contando que la interfaz Web para GX Server la implementaron con GeneXus. ¡Espectacular!