FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin para Harbour/xHarbour Manual de Referencia ORM para Harbour (HDBC)
Posts: 817
Joined: Sun Jun 15, 2008 07:47 PM
Manual de Referencia ORM para Harbour (HDBC)
Posted: Sat Mar 14, 2026 12:03 PM

Manual de Referencia ORM para Harbour (HDBC)

Bienvenido compa帽ero. Este manual detalla el funcionamiento de la capa ORM (Object-Relational Mapping) incluida en el ecosistema HDBC, inspirada en el patr贸n Active Record y en Query Builders modernos. Este ORM proporciona una interfaz elegante, orientada a objetos y fluida para interactuar con tus bases de datos, abstrallendo las sentencias SQL puras y permitiendo trabajar con los registros como si fueran objetos nativos en Harbour.


---

脥ndice

  1. Configuraci贸n y Conceptos B谩sicos
  2. THModel (Active Record y Query Builder)
  3. THCollection (Colecciones de Datos)
  4. Schema Builder (Creaci贸n de Tablas)
  5. Resoluci贸n de Gram谩ticas (THGrammar)

---

1. Configuraci贸n y Conceptos B谩sicos

Para usar el ORM, debes disponer de una conexi贸n activa originada desde el n煤cleo en hxdbc (habitualmente un objeto THDBC). El ORM necesita que se defina la conexi贸n por defecto de forma global por hilo (Thread Static) antes de invocar los modelos.

Activaci贸n Inicial:

Code (harbour): Select all Collapse
#include "hdbc.ch"

// 1. Crear tu conexion C++ habitual
LOCAL oDb := THDbc():new( HDBC_DRIVER_SQLITE )
oDb:connect( "database=app.db" )

// 2. Acoplar globalmente la conexion al nucleo del ORM
THModel():setConnection( oDb )

Al hacer esto, cualquier clase que herede de THModel sabr谩 hacia d贸nde lanzar sus instrucciones.


---

2. THModel (Active Record y Query Builder)

La clase THModel es el coraz贸n del sistema. Implementa tanto las propiedades del modelo (Active Record) como los m茅todos para encadenar instrucciones (Query Builder).

Para crear un modelo personalizado, simplemente hereda de THModel:

Code (harbour): Select all Collapse
// model_user.prg
#include "hbclass.ch"

CLASS User FROM THModel
   // Sobreescribir configuraci贸n (Opcional, ORM asume convenciones)
   DATA table_name INIT "users"
   DATA primary_key INIT "id"
   DATA timestamps INIT .T.       // Controla autom谩ticamente created_at y updated_at
   DATA useSoftDeletes INIT .F.   // Usa deleted_at en vez de borrar f铆sicamente
ENDCLASS

2.1 Acceso a Columnas y Operaciones CRUD (Active Record)

Interact煤a con un 煤nico registro materializando la fila de la base de datos de vuelta al objeto.

Mapeo de Atributos como Propiedades OOP
Las columnas de la tabla se cargan en el diccionario interno del modelo, pudiendo acceder a ellas mediante getAttribute("campo"). Sin embargo, para un c贸digo m谩s limpio y tipado de estilo "OOP puro", puedes enlistar tus columnas exactas en la cabecera de la Clase mediante sentencias DATA. Esto indica a Harbour que conecte las propiedades de la clase f铆sica directamente con la tupla cargada en memoria permitiendo escritura y lectura fluida por punto/dos puntos:

Code (harbour): Select all Collapse
CLASS Category FROM THModel
   DATA table_name INIT "categorias"
   
   // Mapeando expl铆citamente atributos = sintaxis r谩pida OOP oCat:name
   DATA id, name, description 
ENDCLASS

Buscar por ID (find) y Leer Atributos

Code (harbour): Select all Collapse
LOCAL oCat := Category():find( 1 )
IF oCat != nil
   // Modo tradicional
   ? "ID:", oCat:getAttribute( "id" )
   
   // 隆Modo Fluido! (Requiere haber declarado el `DATA name` arriba)
   ? "Nombre:", oCat:name
   ? "Detalle:", oCat:description
ENDIF

Creaci贸n (create / save)
Puedes insertar datos asignando manualmente o masivamente (fill()).

Code (harbour): Select all Collapse
// Opci贸n A: Asignaci贸n masiva (devuelve el objeto guardado)
LOCAL oUser := User():create( { "name" => "Manu", "email" => "manu@test.com" } )

// Opci贸n B: Instanciar y guardar
LOCAL oNuevo := User():new()
oNuevo:setAttribute( "name", "Pepe" )
oNuevo:save()

Actualizaci贸n (update / save)

Code (harbour): Select all Collapse
LOCAL oUser := User():find( 5 )
oUser:setAttribute( "name", "Jose" )
oUser:save() // Lanza autom谩ticamente el UPDATE

Eliminaci贸n (delete)

Code (harbour): Select all Collapse
LOCAL oUser := User():find( 5 )
oUser:delete() // F铆sico o L贸gico si useSoftDeletes es .T.

2.2 Query Builder (Consultas Encadenadas)

Aprovecha la sintaxis fluida para generar complejas b煤squedas sin ensuciar el c贸digo prg con sentencias textuales SQL. Devuelven siempre objetos de tipo THCollection.

Selecci贸n M煤ltiple (where, whereIn, get)

Code (harbour): Select all Collapse
// Todos los usuarios activos
LOCAL oActivos := User():where( "status", "=", "ACTIVE" ):get()

// Buscar en array
LOCAL oVIPS := User():whereIn( "role_id", {1, 2} ):order( "name DESC" ):get()

Consejo de rendimiento (mutable): Por convenci贸n, las queries clonan el objeto base preventivamente cada vez que encadenas un cl谩usula (where()). Si tu cadena es largu铆sima, aplica el modo mutable:
User():mutable(.T.):where(...):orderBy(...):limit(10):get()

Agrupaciones y Limitaciones

Code (harbour): Select all Collapse
LOCAL oStats := User():select( "role_id", "COUNT(*) as total" ) ;
                      :groupBy( "role_id" ) ;
                      :having( "total", ">", 5 ) ;
                      :limit( 10 ) ;
                      :get()

Obtener un solo resultado (first)

Code (harbour): Select all Collapse
LOCAL oPrime := User():where( "score", ">", 1000 ):first()

Agregadores Num茅ricos

Code (harbour): Select all Collapse
LOCAL nMedia := User():where( "status", "=", 1 ):avg( "edad" )
LOCAL nTodos := User():count()

2.3 Utilidades y Upsert

  • upsert( hMatch, hValues ): Muy 煤til. Intenta actualizar si los condicionales en hMatch hacen diana, y si la fila no existe la inserta uniendo los dos Hashes internamente.
  • firstOrCreate( hMatch, hValues ) y firstOrNew( hMatch, hValues ): Buscan un registro. Si no est谩 lo devuelven persistido (OrCreate) o como instancia temporal en memoria (OrNew).
  • pluck( cCol ): Si s贸lo quieres una matriz elemental nativa {"Pepe", "Manu"} puedes saltarte los objetos:
    Code (harbour): Select all Collapse
      LOCAL aNombres := User():where("age",">",18):pluck("name")

3. Relaciones y Caracter铆sticas Avanzadas del Modelo

El ORM brilla especialmente al manejar el entrelazado de datos (Data Relationships) y gestionar el ciclo de vida de los registros con eventos autom谩ticos.

3.1 Relaciones (Relationships)

Con el ORM puedes vincular modelos sin necesidad de programar complejos JOINs manuales. Admite los principales modos de relaci贸n (Has One, Has Many, Belongs To, Belongs To Many).

Code (harbour): Select all Collapse
CLASS User FROM THModel
   // ...
   METHOD profile()
   METHOD posts()
   METHOD roles()
ENDCLASS

// 1 a 1
METHOD profile() CLASS User ; return ::hasOne( "Profile", "user_id" )

// 1 a N
METHOD posts() CLASS User ; return ::hasMany( "Post", "user_id" )

// N a N (Tabla pivote intermedia)
METHOD roles() CLASS User ; return ::belongsToMany( "Role", "role_user", "user_id", "role_id" )

Uso de Relaciones:

Code (harbour): Select all Collapse
LOCAL oUser := User():find(5)

// Lazy Loading (se carga de BBDD al invocarse)
? "Bio de su perfil:", oUser:profile:first():bio
? "Tantos posts escritos:", oUser:posts:count()

// Eager Loading (Precarga 贸ptima para evitar el problema N+1)
LOCAL oUsers := User():with("posts"):all()
oUsers:each({|u| ? u:name, u:posts:count() })

3.2 Accessors (Mutadores de Lectura)

Puedes modificar c贸mo se devuelve un dato de la base de datos sin transformar el valor f铆sico almacenado creando m茅todos get[Columna]Attribute:

Code (harbour): Select all Collapse
CLASS User FROM THModel
   // ...
   METHOD getNameAttribute( cName )
ENDCLASS

METHOD getNameAttribute( cName ) CLASS User 
   return Upper( cName ) + " (Registrado)"

3.3 Observadores (Observers)

Intercepta el ciclo de vida de un modelo encolando disparadores (Triggers) de Harbour puros en el backend, por ejemplo saving o saved.

Code (harbour): Select all Collapse
// Crea una clase de observador
CLASS UserObserver
   METHOD saving( oModel )
ENDCLASS

PROCEDURE saving( oModel ) CLASS UserObserver
   ? "El modelo se va a guardar:", oModel:name
return

// Act铆valo en cualquier parte:
User():observe( "UserObserver" )
LOCAL oU := User():new()
oU:name := "Test"
oU:save() // <- Print: El modelo se va a guardar: Test

3.4 Scopes (脕mbitos de B煤squeda de Reuso)

Si tienes filtros recurrentes (ej: Usuarios que son administradores o que est谩n vivos), a铆sla esa l贸gica en tus modelos usando el prefijo scope:

Code (harbour): Select all Collapse
CLASS User FROM THModel
   // ...
   METHOD scopeActivos( oQuery )
ENDCLASS

METHOD scopeActivos( oQuery ) CLASS User ; return oQuery:where( "status", "=", 1 )

// Uso:
LOCAL oActives := User():scope( "activos" ):order("id DESC"):get()

3.5 Paginadores y Chunking

En lugar de lanzar a la memoria una tabla con 50.000 registros de golpe usando get(), puedes paginarla ordenadamente para un datagrid web u optimizar procesos batch.

Code (harbour): Select all Collapse
// Paginaci贸n tradicional (煤til para APIs)
LOCAL oPager := User():paginate( 50, 1 ) // (CantidadxPagina, Pagina)
? "P谩ginas totales:", oPager["total_pages"]
? "Registros matriz:", Len( oPager["data"] )

// Chunking para procesado silencioso en memoria baja
User():chunk( 1000, {|oColeccion_Trozo| ;
   oColeccion_Trozo:each({|oU| oU:sendEmailLento() }), ;
   .T. ; // Devuelve .F. para abortar procesamiento del chunk
})

---

4. THCollection (Colecciones de Datos)

El resultado masivo de un Query Builder (->get()) no es un Array pelado, es un objeto envoltorio de la matriz THCollection que facilita la iteraci贸n y el pase a formatos como JSON.

Code (harbour): Select all Collapse
LOCAL oUsersColl := User():where("status", "=", 1):get()

? "Registros encontrados:", oUsersColl:count()

// La clase es compatible con bucles FOR EACH
LOCAL oUser
FOR EACH oUser IN oUsersColl
   ? oUser:getAttribute("email")
NEXT

M茅todos funcionales incorporados en la Colecci贸n

  • each( bBlock ): Aplica el CodeBlock a referenciando la instancia.
  • map( bBlock ): Transforma todo y te devuelve un nuevo THCollection.
  • filter( bBlock ): Para depurar resultados traidos en memoria evaluando un .T. / .F..
  • isEmpty() y first().
  • toJson(): Vital para apis web. Genera un String serializado de la matriz de todas las entidades autom谩ticamente. Ex谩ctamente lo que buscas devolver en un endpoint Rest.

---

4. Schema Builder (Creaci贸n y Migraci贸n de Tablas)

HDBC brinda abstracci贸n DDL a trav茅s de THSchema. Te permite programar migraciones y creaciones de base de datos de manera agn贸stica sin lidiar de forma nativa con las diferencias sint谩cticas entre SQLite, MySQL o PostgreSQL.

La creaci贸n de tablas se realiza pasando un bloque de c贸digo al m茅todo create, el cual recibe un objeto "blueprint" (representando la nueva tabla), que usar谩s para definir las columnas:

Code (harbour): Select all Collapse
#include "hdbc.ch"

// En tu bloque de inicializacion o herramienta migratoria:
THSchema():create( "users", {| table | ;
    table:id(), ;                                    // Genera Entero Clave Primaria Autonumerico
    table:string("email", 120), ;                    // Varchar limitado a 120 caracteres
    table:string("password", 255), ;                 // Varchar de 255
    table:boolean("active"), ;                       // Tipos Booleanos
    table:decimal("saldo", 10, 2), ;                 // Monetarios (Precisi贸n 10, Escala 2)
    table:text("bio"), ;                             // Texto Largo / Memo
    table:json("config"), ;                          // Columna especial JSON
    table:timestamps() ;                             // Inyecta `created_at` y `updated_at` (Datetime)
} )

El objeto table recibido en el CodeBlock es en realidad una f谩brica THBlueprint/THField. La capa gramatical traducir谩 transparentemente cada tipo a la versi贸n f铆sica id贸nea seg煤n tu base de datos (Ej: boolean pasar谩 a un INTEGER 1/0 si tu motor base no engloba tipos booleanos predeterminados, como SQLite).

Creaci贸n de Tablas de Forma Expl铆cita (Orientada a Objetos)

Si prefieres no usar el uso de bloques (Closures) o est谩s construyendo la estructura de forma din谩mica durante la ejecuci贸n de tu aplicaci贸n de forma procedimental, puedes instanciar directamente THSchema y agrupar los campos vali茅ndote de la factor铆a THField:

Code (harbour): Select all Collapse
// Opcionalmente puedes destruir tablas previas antes de crearlas
THSchema():new( "productos" ):dropIfExists()

LOCAL oSchema := THSchema():new( "productos" )
oSchema:add( THField():id() )
oSchema:add( THField():string( "nombre", 150 ) )
oSchema:add( THField():decimal( "precio", 10, 2 ) )
oSchema:add( THField():boolean( "activo" ) )
oSchema:add( THField():timestamps() )
oSchema:create()

Tipos de Columnas Disponibles

El Table Blueprint proporciona los siguientes m茅todos para declarar las tipolog铆as de columnas de tus tablas:

M茅todoDescripci贸n del Tipo Resultante
table:id("nombre")Clave primaria Auto-Incremental. Por defecto el nombre es "id".
table:string("c", n)Equivalente a VARCHAR. Acepta un l铆mite de longitud n.
table:integer("c")N煤mero Entero est谩ndar (INTEGER).
table:float("c")N煤mero decimal de coma flotante (FLOAT).
table:decimal("c",p,s)Decimal exacto. p recisi贸n total, s cantidad de decimales.
table:boolean("c")Valores verdadero/falso. Mapeado a enteros seguros 1/0 si el motor lo exige.
table:date("c")脷nicamente fecha (DATE).
table:datetime("c")Fecha y Hora exactas (DATETIME).
table:timestamp("c")Marca temporal SQL (TIMESTAMP o equivalente).
table:text("c")Almacenamiento masivo de texto (TEXT o MEMO).
table:json("c")Estructura JSON si el driver lo encapsula, o String puro en su defecto.
table:timestamps()Helper. Genera autm谩ticamente created_at y updated_at.

Operaciones Posteriores y Mantenimiento

Tambi茅n puedes intervenir esquemas existentes o destruirlos de forma fluida:

Destruir Tablas:

Code (harbour): Select all Collapse
THSchema():drop("users")
THSchema():dropIfExists("users") // Evita excepciones si la tabla ya no exist铆a

---

5. Resoluci贸n de Gram谩ticas (THGrammar)

La clase THGrammar se aloja detr谩s de los bastidores del ORM. Es una capa completamente Abstracta.

Cada driver individual en HDBC (Ej: Postgres, MariaDB) inyectar谩 al ORM su propia extensi贸n (Hija) de THGrammar que sabe c贸mo codificar al dialecto correcto y escapar las palabras clave o valores requeridos (Ej: LIMIT u OFFSET pueden diverger o los comillados \`` contra[]o""`).

No debes instanciarla t煤 expl铆citamente, pero puedes alterar aspectos globales y consultar la delegaci贸n actuando desde tu objeto THDbc:

Code (harbour): Select all Collapse
// Acceder al inyector de tu driver actual
LOCAL oGrammar := THModel():getGrammar()
? oGrammar:sqlListTables()

---

6. Ejemplo Pr谩ctico Completo

Aqu铆 agrupamos todo el conocimiento de los bloques anteriores para levantar un escenario completo: crear las bases de datos de Autores y Libros, definir sus clases enlazadas, inyectar el juego de datos (Altas), leerlos relacionadamente (Listados), mutarlos (Modificaciones) y borrarlos (Bajas).

Code (harbour): Select all Collapse
#include "hdbc_conn.ch"
#include "hbclass.ch"
#include "hdbc.ch"

// ---------------------------------------------------------
// 1. DEFINICI脫N DE MODELOS
// ---------------------------------------------------------
CLASS Author FROM THModel
   DATA table_name INIT "autores"
   DATA id, name
   
   METHOD books() // Relacion 1:N
ENDCLASS

METHOD books() CLASS Author ; return ::hasMany( "Book", "author_id" )

CLASS Book FROM THModel
   DATA table_name INIT "libros"
   DATA id, author_id, title, in_stock
   
   METHOD author() // Inversa N:1
ENDCLASS

METHOD author() CLASS Book ; return ::belongsTo( "Author", "author_id" )

// ---------------------------------------------------------
// 2. FUNCI脫N PRINCIPAL Y EJECUCI脫N
// ---------------------------------------------------------
PROCEDURE Main()
   LOCAL oDb := THDbc():new( HDBC_DRIVER_SQLITE )
   LOCAL oAuthor, oBook, oAuthorsSet
   
   // A. CONEXI脫N AL N脷CLEO
   oDb:connect( "database=library.db" )
   THModel():setConnection( oDb )
   
   // B. GESTI脫N DEL ESQUEMA (Crear si no existe)
   
   // Crear tabla de autores
   THSchema():new( "autores" ):dropIfExists()
   oSchema := THSchema():new( "autores" )
   oSchema:add( THField():id() )
   oSchema:add( THField():string( "name", 150 ) )
   oSchema:add( THField():timestamps() )
   oSchema:create()
   
   // Crear tabla de libros
   THSchema():new( "libros" ):dropIfExists()
   oSchema := THSchema():new( "libros" )
   oSchema:add( THField():id() )
   oSchema:add( THField():integer( "author_id" ) )
   oSchema:add( THField():string( "title", 200 ) )
   oSchema:add( THField():boolean( "in_stock" ):default(.T.) )
   oSchema:add( THField():timestamps() )
   oSchema:create()
   
   // C. ALTAS (Inyecci贸n de Datos)
   // Alta masiva
   Author():create({ "id" => 1, "name" => "Gabriel Garcia Marquez" })
   Author():create({ "id" => 2, "name" => "Isabel Allende" })
   
   // Alta Relacional
   Book():create({ "author_id" => 1, "title" => "Cien A帽os de Soledad", "in_stock" => 1 })
   Book():create({ "author_id" => 1, "title" => "El Amor en Tiempos...", "in_stock" => 0 })
   Book():create({ "author_id" => 2, "title" => "La Casa de los Espiritus", "in_stock" => 1 })

   // D. LISTADOS Y B脷SQUEDAS (Eager Loading para Eficiencia)
   ? "---- LISTADO DE CAT脕LOGO ----"
   oAuthorsSet := Author():with("books"):all()
   
   oAuthorsSet:each({|a| ;
       QOut( "=> Autor: " + a:name ), ;
       a:books:each({|b| ;
          QOut( "   * Libro: " + b:title + " (Stock: " + hb_ValToStr(b:in_stock) + ")" ) ;
       }) ;
   })
   
   // E. MODIFICACIONES (Update)
   ? "---- ACTUALIZANDO STOCK ----"
   oBook := Book():where("title", "LIKE", "%Amor en Tiempos%"):first()
   IF oBook != nil
      oBook:in_stock := 1 // Reposici贸n de libro
      oBook:save()
      ? "Stock reabastecido de:", oBook:title
   ENDIF

   // F. BAJAS (Delete)
   ? "---- ELIMINANDO REGISTRO ----"
   oAuthor := Author():find( 2 ) // Buscando a Isabel Allende
   IF oAuthor != nil
      // Borrar todos sus libros secuencialmente primero usando su relaci贸n
      oAuthor:books:each({|b| b:delete() })
      
      // Borrar el autor
      oAuthor:delete()
      ? "Autor y libros asociados borrados correctamente."
   ENDIF
   
   THModel():end()
   oDb:disconnect()
RETURN

---

鈿欙笍 Fin del manual ORM. Combina THModel con validaciones s贸lidas (rules) para gobernar las l贸gicas de negocio con elegancia y seguridad. 🚀

______________________________________________________________________________

Sevilla - Andaluc铆a
Posts: 309
Joined: Wed Mar 28, 2018 04:38 PM
Re: Manual de Referencia ORM para Harbour (HDBC)
Posted: Mon Mar 16, 2026 07:33 AM

buen trabajo!

--------

驴 Y porque no ?

驴 And why not ?

Continue the discussion