Archive for October, 2008

Introducción al modelo de datos (II)

Sunday, October 19th, 2008

Este es el segundo artículo sobre modelo de datos, en este estudiaremos las clases TFactura y TLineasFactura.

Desde la clase TFactura accederemos a objetos de las clases TCliente y TLineasFactura; y de la clase TLineasFactura accederemos a un objeto de la clase TArticulo.

Observando el código de las clases notaremos que el nivel de automatización al que se llega y que nos facilitará mucho el trabajo de codificación de nuestra aplicación.

CLASS TFactura FROM TQuery

DATA cConsulta

DATA oCli

DATA oLin

METHOD Init( cQuery )

METHOD Delete( nId_Articulo )

METHOD Change( oSender )

ACCESS oCliente

ACCESS oLineas

END CLASS

METHOD Init( cQuery ) CLASS TFactura

DEFAULT cQuery TO “SELECT * FROM cabfac”

::New()

::OnChange := { | oSender | Change( oSender ) }

::cConsulta := cQuery

::Query( ::cConsulta )

RETURN Self

METHOD Delete( nId_Factura ) CLASS TFactura

IF !Empty( nId_Factura )

::Command( “DELETE FROM cabfac “ + ;

“WHERE id = “ + Str( nId_Factura ) + “ “ + ;

“LIMIT 1” )

::Change()

END IF

RETURN Nil

METHOD Change( oSender ) CLASS TFactura

::oCli := NIL

::oLin := NIL

RETURN Nil

METHOD oCliente CLASS TFactura

IF Empty( ::oCli )

::oCli := TCliente():Init( “SELECT * FROM cliente “ + ;

“WHERE id = “ + Str( ::Id_Cliente ) )

END IF

RETURN ::oCli

METHOD oLineas CLASS TFactura

IF Empty( ::oLin )

::oLin := TLineasFactura():Init( “SELECT * FROM detfac “ + ;

“WHERE id_factura = “ + Str( ::Id ) + ” ” + ;

“ORDER BY linea” )

END IF

RETURN ::oLin

//——————————————————————-//

CLASS TLineasFactura FROM TQuery

DATA cConsulta

DATA oArt

METHOD Init( cQuery )

METHOD Delete( nId_Articulo )

METHOD Change( oSender )

ACCESS oArticulo

END CLASS

METHOD Init( cQuery ) CLASS TLineasFactura

DEFAULT cQuery TO “SELECT * FROM detfac”

::New()

::OnChange := { | oSender | Change( oSender ) }

::cConsulta := cQuery

::Query( ::cConsulta )

RETURN Self

METHOD Delete( nId_Factura, nLinea ) CLASS TLineasFactura

IF !Empty( nId_Factura )

::Command( “DELETE FROM derfac “ + ;

“WHERE id_factura = “ + Str( nId_Factura ) + “ “ + ;

“AND linea = ” + Str( nLinea ) + ” ” + ;

“LIMIT 1” )

::Change()

END IF

RETURN Nil

METHOD Change( oSender ) CLASS TLineasFactura

::oArt := NIL

RETURN Nil

METHOD oArticulo CLASS TLineasFactura

IF Empty( ::oArt )

::oArt := TArticulo():Init( “SELECT * FROM articulo “ + ;

“WHERE id = “ + Str( ::Id_Articulo ) )

END IF

RETURN ::oArt

//——————————————————————-//

Para acceder a una factura, símplemente tendremos que escribir el siguiente código:

oFactura := TFactura():Init( “SELECT * FROM cabfac WHERE id = ” + Str( nNumFac )

para acceder a los datos del cliente de una factura:

oCliente := oFactura:oCliente

MsgInfo( oCliente:Nombre )

para acceder a las lineas de la factura:

nTotal := 0

oFactura:oLineas:GoTop()

WHILE !oFactura:oLineas:Eof()

nTotal += oFactura:oLineas:Cantidad * oFactura:oLineas:Precio

oFactura:oLineas:Skip()

END DO

MsgInfo( nTotal )

para acceder al nombre de un artículo:

oFactura:oLineas:oArticulo:Nombre

Con esto terminamos el segundo artículo dedicado a la introducción al modelo de datos. En el próximo estudiaremos como ir mejorando poco a poco las estructuras de datos para, por ejemplo, poder refrescar las consultas, aplicar el uso de vistas, crear métodos que proporcionen totales, etc. Veremos como el modelo básico que hemos definido en estos dos artículos puede completarse hasta conseguir una funcionalidad tan amplia que aumentará el ahorro y claridad posterior de nuestro código.

Introducción al Modelo de Datos (I)

Friday, October 17th, 2008

Llevo tiempo pensando en lo útil que resultaría contar con un modelo de datos cómodo que permita facilitar la labor de programación y posterior mantenimiento de nuestras aplicaciones. Coincido con mucha gente que la construcción de un modelo de datos es una tarea pesada que requiere mucha abstracción mental y escribir mucho código, pero también he de admitir que un modelo de datos bien construido ahorra muchas horas de trabajo.

He dividido el artículo en dos para hacerlo menos pesado, aunque al terminar este creo que todos los lectores sabrán por donde van los tiros y que va a pasar en la segunda parte.

Todo el código que pongo aquí está escrito con el único objetivo de ejemplificar un modelo de datos y, desde luego, puede ser mejorado; he suprimido a propósito controles de errores y otras cosas a fin de que se vea claramente como funcionan las clases que lo componen.

El ejemplo que vamos a usar para construir nuestro modelo de datos es una sencilla facturación:

  • Tabla de familias de artículos (id, nombre)
  • Tabla de artículos ( Id, Id_familia, nombre, pvp )
  • Tabla de clientes( Id, nombre, domicilio, telefono )
  • Tabla de cabecera de facturas ( Id, fecha, Id_cliente, iva )
  • Tabla de lineas de facturas ( Id_factura, linea, Id_articulo, cantidad, precio )

Comencemos por identificas las clases que van a constituir nuestro modelo de datos:

  • Clase TFamilia
  • Clase TArticulo
  • Clase TCliente
  • Clase TFactura
  • Clase TLineasFactura

Para el código de los ejemplos usaré xHarbour y Xailer. Todas las clases heredarán de una clase principal y abstracta llamanda TQuery, que es una clase que simula un datasource. La clase TQuery está programada por mí basándome el los wrappers que construyó Manu Expósito en Eagle 1 para acceder de forma nativa a MySQL. Creo que no existen problemas para poder heredar del sistema de datasource de cualquier otro producto. Quien lo desee puede contactarme para que le envíe el código fuente de TQuery.

Las primeras clases que vamos a definir son las relacionadas con las tablas maestras, empezando por las que no contienen referencias a claves foráneas de otras tablas, en nuestro caso, las clases TFamilia y TCliente.

CLASS TFamilia FROM Tquery

DATA cConsulta

METHOD Init( cQuery )

METHOD Delete( nId_Familia )

END CLASS

METHOD Init( cQuery ) CLASS TFamilia

DEFAULT cQuery TO “SELECT * FROM famila”

::New()

::cConsulta := cQuery

::Query( ::cConsulta )

RETURN Self

METHOD Delete( nId_Familia ) CLASS TFamilia

IF !Empty( nId_Familia )

::Command( “DELETE FROM familia “ + ;

“WHERE id = “ + Str( nId_Familia ) + “ “ + ;

“LIMIT 1” )

END IF

RETURN Nil

//——————————————————————//

CLASS TCliente FROM Tquery

DATA cConsulta

METHOD Init( cQuery )

METHOD Delete( nId_Cliente )

END CLASS

METHOD Init( cQuery ) CLASS TCliente

DEFAULT cQuery TO “SELECT * FROM cliente”

::New()

::cConsulta := cQuery

::Query( ::cConsulta )

RETURN Self

METHOD Delete( nId_Cliente ) CLASS TCliente

IF !Empty( nId_Cliente)

::Command( “DELETE FROM cliente “ + ;

“WHERE id = “ + Str( nId_Cliente ) + “ “ + ;

“LIMIT 1” )

END IF

RETURN Nil

//——————————————————————//

Constituiremos ahora la clase para controlar la tabla de artículos, que como podemos observar, contiene una clave foránea a la tabla de familias (Id_familia); por lo tanto, desde nuestro modelo de datos, un objeto oArtículo de la clase TArtículo tendrá que poder acceder a los datos de la familia de cada artículo, facilitando posteriormente las labores de programación de nuestra aplicación.

CLASS TArticulo FROM TQuery

DATA cConsulta

DATA oFam

METHOD Init( cQuery )

METHOD Delete( nId_Articulo )

METHOD Change( oSender )

ACCESS oFamilia

END CLASS

METHOD Init( cQuery ) CLASS TArticulo

DEFAULT cQuery TO “SELECT * FROM articulo”

::New()

::OnChange := { | oSender | Change( oSender ) }

::cConsulta := cQuery

::Query( ::cConsulta )

RETURN Self

METHOD Delete( nId_Articulo ) CLASS TArticulo

IF !Empty( nId_Articulo)

::Command( “DELETE FROM articulo “ + ;

“WHERE id = “ + Str( nId_Articulo ) + “ “ + ;

“LIMIT 1” )

::Change()

END IF

RETURN Nil

METHOD Change( oSender ) CLASS TArticulo

::oFam := NIL

RETURN Nil

METHOD oFamilia CLASS TArticulo

IF Empty( ::oFam )

::oFam := Tfamilia():Init( “SELECT * FROM familia “ + ;

“WHERE id = “ + Str( ::Id_Familia ) )

END IF

RETURN ::oFam

Vamos a detenernos un instante a estudiar que tienen estas clases dentro. De forma general, todas las clases llevan una DATA que contiene la consulta efectuada (cConsulta) y los MÉTODOS Init() y Delete().

El método Init() es el encargado de construir y retornar el objeto correspondiente. En su interior nos encontramos instrucciones que, a parte de establecer un valor por defecto para la consulta, también realizan:

  1. Llamar al método New() de la superclase para la construcción del objeto.

  2. Llamar al método Query() de la superclase para ejecutar la consulta y cargar los datos correspondientes.

El método Delete() es el encargado de eliminar una fila de la tabla correspondiente. En caso de usar bases de datos SQL recomiendo tener bien definida la integridad referencial. En otro tipo de almacenes de datos, en este método habrá que incluir el código necesario para eliminar información de las tablas que contengan información de la fila que estamos eliminando.

Estudiando la clase TArticulo podemos observar algunas diferencias muy significativas: aparece una DATA aFam, el método Change() y el método especial ACCESS oFamilia.

Mediante la palabra reservasa ACCESS de xHarbour se define un método especial que se comportará como una DATA y que además ejecuta su código cada vez que se instancia.

La DATA oFam contendrá un objeto de la clase TFamilia descrita anteriormente.

El método Change() se ejecutará cada vez que dispare el evento OnChange() de la clase TQuery. Este evento responde cada vez que se produce un cambio al añadir, actualizar, borrar, navegar o recargar datos de la consulta. Este método pone a NIL la data oFam para controlar un tipo de recursividad que veremos más adelante.

El método ACCESS oFamilia se ejecutará cada vez que accedamos a él, ejecutando la consulta que carga los datos de la familia relacionada con el artículo actual. Para evitar la recursividad y que se ejecuten consultas constantes (perdida de velocidad :-(), miramos si está vacío.

Con lo visto hasta este momento creo que ya habréis podido adivinar que el modelo de datos perimite acceder de forma automática a los datos de tablas relacionadas sin necesidad de escribir código adicional. Comparemos:

  1. Forma tradicional sin modelo de datos:

oArticulo := TQuery():New( “SELECT * FROM articulo” )

WHILE !oArticulo:Eof()

oFamilia := Tquery():New( “SELECT * FROM familia “ + ;

“WHERE id = “ + Str( oArticulo:Id_Familia ) )

MsgInfo( oFamilia:Nombre )

oArticulo:Skip()

END DO

  1. Usando el modelo de datos:

oArticulo := TArticulo():New( “SELECT * FROM articulo” )

WHILE !oArticulo:Eof()

MsgInfo( oArticulo:oFamilia:Nombre )

oArticulo:Skip()

END DO

Es obvio que usando un modelo de datos conseguimos claridad y automatización en la mayoría de los procesos que tenemos que realizar dentro de nuestros programas.

En la segunda parte de este artículo veremos como se automatiza la clase Tfactura.