Etiqueta

1 de octubre de 2014

Uso de las clases "table"

Los desarrolladores a menudo nos preguntamos si no sería mejor utilizar clases "object" en lugar de clases "table" a la hora de construir código SQL, ya que al fin y al cabo, es posible programar en modo orientado a objetos, aún sin utilizar clases "table".

A primera vista, esto parece una propuesta razonable, ya que al utilizar las clases "object", disponemos de acceso directo al código y no será necesario escribir métodos con el código SQL sobre la clase "table", sino que podremos hacerlo en cualquier lugar.

De hecho, (según me consta) es un práctica extendida entre los programadores el hacer uso de las clases "schema" directamente, en lugar usar clases "table" conectadas a clases "schema". Seguramente debido a que con las clases "table", el programador debe decidir si introducir el código SQL a ejecutar dentro de la propia clase o simplemente hacer uso de los métodos y propiedades predefinidos, además de que, en cierto modo, las clases "table" son similares a clases "object".

Esencialmente la diferencia está, en que las clases "table" tienen propiedades adicionales. Por ejemplo; cuando una instancia, de una clases "table" ha sido definida con las columnas de una variable de tipo "list" o "row" y añadido a ella los métodos SQL que deseemos. Debemos tener en cuenta que dicha instancia es también un objeto de datos, es decir, una variable "list" o "row". Esto significa que en realidad estamos incluyendo métodos dentro de objetos de datos. El ejemplo siguiente, muestra un método en una clase "table", del cual obtenemos su valor de retorno:

If myRow.$checkFields()
   Do myRow.$insert()
Else
   OK message "El campo name esta vacío"
Endif


El método $checkFields() en la clase "table" podría ser:

If len($cinst.name) = 0 ;;name está vacío
   Quit method kFalse
Else
   Quit method kTrue
Endif

En nuestro ejemplo $cinst hace referencia a la fila actual de la variable "list" utiliza al definir la clase "table".

Algunos podrían argumentar que puede lograrse el mismo resultado usando una clase "object". Pero, esto no sería estrictamente cierto ya que requeriría del uso de otra capa de código, además del necesario uso de la clase "object" para acceder a la variable "list" o "row", mientras que en la clase "table" se aúnan ambas cosas.

Además (como en el caso de las clases "object") se puede hacer uso de los métodos predefinidos a la hora de gestionar la "list" o "row" de datos ya que, como tal, las clases "table" son autónomas. Esto nos permite además hacer uso de las propiedades y métodos de uso general, al mismo tiempo que decidir si haremos uso o no, de los definidos por nosotros, al actuar como super-clase de la clase "table". Atendiendo a nuestro ejemplo, colocaríamos el método $checkFields() en la super-clase, con lo que tendríamos la opción de anular este método desde la clase "table" derivada en caso necesario, para ello simplemente colocaríamos el comando siguiente en el método en la super-clase:

Quit method kTrue

En otras palabras, si no se hereda el método checkFields() de la clase "table", siempre devolverá kTrue.

Por supuesto usted es libre de usar clases "object" o "table", o ambas a la vez según lo considere oportuno, a la hora de incorporar el código SQL a su aplicación. La única cosa que deberá tener siempre en cuenta es, que la definición que hagamos mediante el método $definefromsqlclass() sobre la variable "list" o "row", será quien determine, donde deberá añadirse el código SQL. Por ejemplo:

Do myList.$definefromsqlclass($tables.T_Address,'Name','Firstname','City','ADDRESS_ID')

En éste ejemplo, "myList" es definida mediante la clase "table" "T_Address" y con las columnas; "Name", "Firstname", "City" y "ADDRESS_ID".

Cuando desee escribir su propio código SQL con estos nombres de columna, deberá hacerlo mediante la implementación de un método dentro de la propia clase "table" y que hará uso de su propio "statementobject" al ejecutarse. Tenga en cuenta que para que esto funcione, el objeto de sesión (sessionobject) deberá haber sido asignado a la clase "table", cosa que (dicho sea de paso) habremos hecho en el método $construct() de la super-clase.

Método: $loadExample()

Begin Statement
    STA: SELECT [$cinst.$selectnames()]
    STA FROM ... (su propio SQL)
End Statement
Do $cinst.$statementobject().$prepare()
Do $cinst.$statementobject().$execute() Returns Ok
If not(Ok)
    Do $cinst.$sqlerror()
    Quit method kFalse
Else
    Do $cinst.$fetch(kFetchAll) ;; Cargas todos los registros en el propio objeto
    Quit method kTrue
Endif

Note que $cinst.$selectnames() devolverá los datos de las columnas de la variable "list" o "row" utilizada en el momento de definir el instancia para la clase "table".

El método sería invocado desde la clase "table", del modo siguiente:

If myList.$loadExample()
   Do $cinst.$redraw()
Else
   Ok message "No se encontraron datos: [myList.$getErrorText()]
Endif

Utilizando este enfoque, el objeto cargará sus datos de forma independiente. al mismo tiempo que nos permite obtener el mensaje de error que pudiera producirse, lo cual también podría ser implementado en la super-clase si optáramos por un control de errores más generalizado, algo así como:

Método: $getErrorText()

Quit method $cinst.$statementobject().$nativeerrortext()

Esto completaría el círculo, ya que ahora estaríamos accediendo directamente las propiedades del "statementobject" ubicado dentro del propio objeto de datos. El método $getErrorText() devuelve el texto nativo del error, para ser mostrado el mensaje.

Pensemos ahora en otra ventaja al usar las clases "table", cuando hacemos uso de nuestros propios métodos para insertar datos solemos hacer uso de variables "bind" (ya hemos hablado de ellas en un artículo anterior de éste blog). Las variables "bind" hacen uso de caracteres especiales, con el fin de ahorrarnos la necesidad de poner comillas para encerrar textos. Pero en el caso de las clases "table", y si hacemos uso del método $insert() por defecto, (es decir no personalizado) el objeto utilizará automáticamente las variables tomándolas directamente de la variable "list" o "row" por lo que no tendremos que preocuparnos de nada en absoluto.

Además, el uso de clases "table", no impide el control sobre el código SQL que enviamos al servidor. Para ejemplo:

Calculate mySQLText as $cinst.$statementobject().$sqltext

Si decidiéramos no pasar ciertas columnas, podríamos excluirlas de los $insert() desde el método $construct() de la super-clase "table", del modo siguiente:

Do $cinst.$cols.ident.$excludefrominsert.$assign(kTrue)

En este ejemplo, la columna "ident" sería excluida de la inserción.

Por cierto, el método integrado $sqlerror() en una clase "table" es ejecutado automáticamente, cuando cualquiera de los métodos incluidos tales como $insert(), $update(), $delete() o $select() falla por cualquier razón. Por lo que, podría tener sentido anular este método también de la super-clase "table", creando un controlador de errores SQL más genérico en nuestra aplicación, en el manual Omnis Studio en Castellano podrá encontrar más información al respecto.

24 de septiembre de 2014

Uso de variables vinculadas (Bind) en sentencias SQL

Al ejecutarse (por defecto) cualquiera de los métodos disponibles para una instancia de tipo "Table", como por ejemplo $select() o $insert(), Omnis genera dinámicamente la sentencia SQL correspondiente, pero antes de su ejecución, compone (proceso conocido como “tokenize”) todos y cada uno de los elementos (variables, valores, etc) que conformaran la sentencia SQL que enviará al servidor.

Particularmente, esto significa que Omnis deberá localizar las referencias a las variables "bind" que hayamos utilizado, es decir las usadas mediante la notación @[variable]. Cuando Omnis "tokenize" la sentencia, tendrá en cuenta el ámbito en el que espera encontrar las variables referenciadas. En el caso de una instancia de tipo "Table", el ámbito de aplicación será, el de la propia instancia.

Llamamos "ámbito de aplicación" al medio en que podremos encontrar las variables declaradas, según sean "Task" (Nivel de Tarea), "Class" (Nivel de clase), "Local" (Nivel local), "Parameter" (Equivalente al nivel local) o "Hash" (Globales).

En el caso de que se trate de una lista creada en la instancia "Table" y definida mediante una clase "schema" o "query", se permitirá el uso de los siguientes ámbitos:

  • @[#...] - Una variable "hash", como por ejemplo @[#S1], o @[#10]. Con la excepción de #D, #T, #CT que son técnicamente "macro-hash" (evaluadas en el momento de ser referenciadas) y no pueden ser evaluadas durante el proceso "tokenize". Si necesitamos hacer uso de la fecha actual, podríamos optar por lo siguiente: Calculate lvCurrentDate as #D, para después, usar @[lCurrentDate].
  • @[taskvar] - una variable de tarea, siempre y cuando la tarea que ejecuta y define la lista sean la misma.
  • @[$cinst.column] - cuando se trata de una "row" definida con una clase SQL, "column", sería una de las columnas de la "row".
  • @[$cinst.N.column] - cuando se trata de una lista definida con una clase SQL, "column", sería una de las columnas de la lista, "N" sería un número de línea literal. Por ejemplo, @[$cinst.5.column] haría referencia al valor de "column" en la línea 5. No está permitido el uso de otras notaciones entre corchetes, dentro de éstas, es decir, la referencia @[$cinst.[lvRowNo].column] no es admitida como variable "bind". Una solución a esto, podría ser, hacer uso del comando Calculate $cinst.$line as lvRowNo, para después, usar @[$cinst.column].

A continuación mostramos un ejemplo sobre como hacer uso de una variable "bind" al obtener datos, en correspondencia con los almacenados en la columna "Col3" de una lista:

Begin Statement
   Sta: Select * from Table
   Sta: Where Col = @[iList.Col3]
End Statement

Do lStatementObj.$prepare() Returns lStatus
For iList.$line from 1 to iList.$linecount
   Do lStatementObj.$execute() return lStatus
   Do lStatementObj.$fetch(iRow)
   .....
End for

En el caso de que se trate de una lista creada en la instancia "Table" y definida mediante una clase "schema" o "query", también podría utilizar:

  • @[$cinst.iVar] donde "iVar" sería una variable de instancia en la clase "Table" y que podría haber sido configurada mediante la ejecución previa de un método, como por ejemplo MyList.$setbind('valor') antes de invocar al MyList.$select(), MyList.$insert(), etc

En el ejemplo siguiente, se invoca al método $setbind que contendría a su vez el parámetro "pVar" y un sola línea "Calculate iVar as pVar". Luego, desde fuera de la clase "Table" tendríamos lo siguiente:

Calculate lFruit as 'Oranges'
Do lList.$definefromsqlclass('MyTable')
Do lList.$setbind(lFruit)
Do lList.$sessionobject.$assign(iSessionObj)
Do lList.$select('WHERE Fruit=@[$cinst.iVar]')
Do lList.$fetch(kFetchAll)

La formula descrita en éste artículo nos permite el uso de variables que de otra manera no estarían a nuestro alcance desde la clase "Table".

17 de septiembre de 2014

Uso de "Item reference"

En éste artículo hablaremos un poco sobre las variables de tipo "Item reference", que no deben ser (en ningún caso) confundidas con las de tipo "Object reference" de las que ya hemos hablado en un anterior artículo, las variables "Item reference" puede referenciar a un punto concreto dentro del árbol de objetos y han sido creadas para contener cadenas de notación largas, tales como:

Set reference myRef to $root.$iwindows.wTest.$objs.Tabpane.$objs.Button
De éste modo, puedo simplificar la notación con la que hago referencia a las propiedades de un objeto, como (por ejemplo) para el caso expuesto mediante el comando de asignación anterior:

Do myRef.$text.$assign('Mi botón')

En el capítulo 3 del manual de Programación Omnis en castellano, podrá encontrar una descripción de cómo utilizar este tipo de variables, así como del comando utilizado para la asignación de su posibles valores, el comando "Set Reference".

 

Eliminación de un "Item reference"


Por supuesto las variables del tipo "Item reference" pueden ser re-asignadas, es decir modificar o alterar su contenido, mediante la instrucción:

Set reference myRef to $root.$iwindows.wTest.$objs.Tabpane.$objs.Button2

"myRef" ahora estaría apuntando a un botón diferente.

 

Pero, ¿cómo se elimina un "Item reference"?


El comando...

Calculate myRef as #NULL

...produciría un error en la notación resultante y...

Set reference myRef to #NULL

...generaría una referencia válida, pero, hacia la constante #NULL.

Así que la respuesta es:

Set reference myRef to

(Sin indicar notación alguna) lo cual creará una referencia vacía, pero sin errores.

 

Verificación de "Item reference"


Con frecuencia será necesario comprobar, si la notación contenida en una variable "Item reference" sigue siendo válida. En la mayoría de los casos bastará con utilizar el siguiente código:

If myRef
     OK message Comprobación de referencias {La referencia es correcta}
Else
     OK message Comprobación de referencias {La referencia no es correcta}
End If

La instrucción "If" no precisa la inclusión de cálculo alguno (excepto para el caso expuesto más abajo); "If isnull(myRef)" o "If not(myRef)" no siempre produce los resultados esperados. Si la cuestión es tratar el caso de que la referencia no es correcta, es mejor usar la condición "Else" y programar en ese punto lo que proceda.

En ocasiones, puede ser que lo que deseemos sea crear una referencia a una variable. En este caso, deberemos agregar la notación ".$ref' en el final del nombre de la variable y en el momento de su construcción. De modo que, el código para crear una referencia a una variable sería:

Set reference myRef to ivString.$ref

Pero en estos casos, se revela un pequeño problema:

En el caso de la consulta mostrada anteriormente "If myRef" no funcionará cuando se trata de referencias a variables, es decir, siempre deriva hacia la rama del "Else", aún cuando "myRef" esté apuntando hacia una variable totalmente correcta. De modo que para comprobar la validez de éste tipo de referencias, será necesario usar lo siguiente:

If myRef.$ident

Esto devolverá kTrue si la variable "ivString" existe realmente, incluso aunque contenga el valor #NULL. Por cierto, la consulta "If myRef.$ident" también funciona cuando se está haciendo referencia a una instancia.

Lamentablemente no es posible utilizar éste método de comprobación para ver si la referencia a un grupo de objetos se ha establecido correctamente. Por ejemplo, si se ha definido previamente myRef con:

Set reference myRef to $clib.$classes

En el caso de "If myRef.$ident" siempre derivará hacia la rama "Else". No obstante y para estos casos, la consulta "If myRef" funcionará sin ningún problema. Por lo tanto, si deseamos aunar ambas posibilidades (es decir referencias a variables y a otras notaciones) deberíamos realizar la consulta del siguiente modo:

If myRef | myRef.$ident

Sin embargo, normalmente conoceremos de antemano si la referencia en cuestión apunta hacia una instancia, hacia una variable o hacia un grupo.