Perl/Rose::DB
Módulo | Rose::DB |
---|---|
Versión | 0.769-1 |
Uso | Construcción de programas y aplicaciones |
Propósito | Gestor de relaciones entre objetos y bases de datos relacionales |
Sumario
Enlaces y referencias
- Ref: Rose::DB
- Tutoriales:
Pendiente
Esta sección incluye aquellos conceptos que tengo pendientes de resolver o estudiar de una forma más satisfactoria que la actual:
- Inicialización
- Definir un mecanismo de inicialización de aplicaciones que usen esta biblioteca.
- Errores
- Definir la generación de errores
- Incorporar esta generación a los módulos generales de aplicación
- Gestor de objetos individuales (Rose::DB::Object)
- Relaciones
- Personalización de métodos
- Tipos de datos compartidos para una determinada aplicación
- Relaciones
- Gestores de objetos múltiples (Rose::DB::Object::Manager)
- Estudiar la personalización de los métodos de clase automáticos, especialmente en el nombre.
Conceptos
La herramienta Rose::DB está basada en la premisa de la abstracción. Emplea para ello varias clases con un comentido concreto.
- Rose::DB corresponde a la base de datos
- Rose::DB::Object representa una fila de una tabla.
- Rose::DB::Object::Manager gestiona múltiples objetos Rose::DB.
Rose::DB
Rose::DB es una capa de abstracción para la funcionalidad proporcionada por DBI pero sin heredar nada de ella; en su lugar la clase Rose::DB tiene una referencia a un objeto DBI como atributo.
La herramienta añade también abstracción sobre las bases de datos finales y para ello identifica las fuentes de datos empleando un espacio de nombres a dos niveles consistente en un dominio y un tipo. Ambos son textos arbitrarios y si no se especifican se emplean valores predefinidos (que pueden obtenerse mediante los métodos de clase default_domain() y default_type()).
El autor recomienda utilizar el dominio para identificar el entorno (desarrollo, pruebas, producción, ... ) y el tipo para indicar el contenido lógico de la base de datos (informes, archivo, principal, ...).
Rose::DB::Object
Esta clase base está diseñada para encapsular una única fila en una tabla de la base de datos con la siguientes capacidades:
- Crear una fila en la base de datos (método save de un objeto).
- Inicializar un objeto o cargarlo desde una fila de la base de datos.
- Modificar una fila desde un objeto.
- Borrar una fila de la base de datos.
- Recuperar la información de objetos externos a los que se referencia mediante claves en el objeto (foreign keys) o mapas de relaciones.
La carga de los objetos se realiza bien mediante la clave primaria, bien mediante una clave única.
Rose::DB::Object::Manager
Esta otra clase base (hermana de la anterior) está pensada para tratar con varias filas de una base de datos, complementando a Rose::DB::Object y separando conceptualmente los dos aspectos: gestionar una fila como entidad y manipular varias como un conjunto.
Con ella se puede hacer:
- Recuperar un conjunto de objetos desde la base de datos empleando condiciones y límites.
- Iterar sobre un conjunto de objetos paso a paso.
- Cargar objetos referenciados junto con los indicados expresamente.
- Empleando consultas complejas se puede también:
- Contar el número de objetos resultantes.
- Actualizar los objetos.
- Borrar los objetos en la base de datos.
Claúsula de búsqueda
- Ref: build_select
Las claúsulas de búsqueda permiten construir sentencias SQL de selección de filas mediante la parametrización de sus partes.
- columns HASHREF
- Referencia a una tabla hash indexada por el nombre de tabla y que contiene referencias a listas de nombres de columnas de cada una. Estas serán las columnas recuperadas en la consulta.
- logic "AND" | "OR"
- Unión predeterminada para las claúsulas de la consulta. Si no se especifica se utiliza siempre AND.
- query PARAMS
- Condiciones de selección de filas formadas por una referencia a una lista de pares nombre/valor, referencias a valores escalares o referencias a otras listas.
Mapa de métodos disponibles
Rose::DB::Object
Cada columna definida en el objeto tiene asociada un método de lectura y escritura con el nombre de la misma. La siguiente relación de métodos constituyen una lista de nombres reservados por lo que si la tabla contiene alguno de ellos tendrán que utilizarse un alias en la definición.
Método | Descripción y parámetros |
---|---|
db [DB] | Obtiene o cambia el objeto Rose::DB empleado para acceder a la base de datos que contiene la tabla correspondiente a la clase.
|
dbh [DBI] | Obtiene o cambia el manejador DBI.
|
error | Devuelve el mensaje de error derivado de la última operación realizada. |
not_found | Retorna verdadero si la última llamada a load ha fallado debido a que no existe el objeto según clave primaria o única. Retorna falso en caso contrario. |
meta | Retorna una instancia a Rose::DB::Object::Metadata que describe la tabla, columnas y claves asociadas con la clase. |
delete [%params] |
Borra la fila representada por el objeto actual. Éste debe haber sido cargado previamente vía load o tener definida la clave primaria. Retorna verdadero si ha tenido éxito y falso en caso contrario. Los parámetros pueden ser:
|
insert [%params] |
Inserta el objeto actual en la base de datos. Sólo debe emplearse cuando se quiere forzar la inserción del objeto en lugar de su actualización. Los parámetros pueden ser:
|
load [%params] |
Carga el objeto desde la fila correspondiente en la base de datos empleando bien la clave primaria, bien una clave única. Las columnas se inicializan con los valores de la fila. Retorna uno de estos tres casos:
Los parámetros pueden ser:
|
update |
Actualiza el objecto actual en la base de datos. Sólo debe emplearse cuando se desee forzar una operación de actualización (update) en lugar de una inserción (insert). Los parámetros pueden ser:
|
save [%params] |
Guarda el objeto actual en la base de datos. En ausencia de parámetros, si el objeto ha sido previamente cargado desde la base de datos, la fila será actualizada (update). En caso contrario la fila será insertada (insert). Retorna una referencia al objeto si la operación ha tenido éxito y un valor falso en caso contrario. Si la operación de inserción ha generado un nuevo valor en la clave primaria (auto generado) éste estará reflejado en la correspondiente columna. Los parámetros posibles son:
|
prepare_cached
Cuando una de las operaciones anteriores requiera construir una instrucción SQL y acepta éste parámetro, un valor verdadero determina el empleo del método DBI prepare_cached cuando se compila la instrucción SQL correspondiente. Dicho método verifica que la instrucción ya exista compilada y no vuelve a contactar con el servidor SQL.
En caso contrario, si el parámetro es falso, se utiliza el método DBI prepare que -según el controlador de base de datos- puede llegar a contactar siempre con el servidor.
delete cascade
El borrado en cascada significa que se incluyen en el mismo las filas relacionadas con la actual. El tipo puede ser delete o null, siendo el valor 1 un sinónimo de delete.
Según el tipo de relación y el tipo de borrado tendremos:
Relación | Tipo delete | Tipo null |
---|---|---|
Uno a muchos |
Se borran todas las filas en la tabla externa que referencia a la fila que estamos borrando. |
Se asigna a nulo el valor de la columna, en todas las filas de la tabla externa, que contengan una referencia a la fila que borramos. |
Muchos a muchos |
Borrado de todas las filas en la tabla mapa que referencien a la fila que borramos. |
Asignación de nulo en todas las columnas de la tabla mapa que referencia a esta fila. |
Uno a uno |
Borrado de la fila correspondiente en la tabla externa. |
Asignación de valor nulo en la columna que referencia a ésta en la tabla externa. |
En todos los casos se crea una nueva transacción si no existe una activa. Cualquier fallo en cualquier operación provoca la cancelación de todas las demás.
save cascade
La actualización de la base de datos en cascada afecta también a aquellos objetos que tengan una relación con el que provoca al acción, que hayan sido cargados previamente y que tengan algún valor modificado.
Este procedimiento se lleva a cabo recursivamente sobre todos los sub-objetos y dentro de una transacción. Si alguna de las operaciones falla todo el grupo lo hace.
Rose::DB::Object::Manager
Método | Descripción y parámetros |
---|---|
delete_objects |
Borra filas de una tabla basándose en los parámetros recibidos. Estos pueden ser además de los comunes:
|
get_objects |
Obtiene filas de la base de datos en forma de objetos Rose::DB::Object según los parámetros recibidos. El valor retornado es una referencia a una lista de objetos (posiblemente vacía si no ha encontrado ninguno) o un valor nulo (undef) si ha habido algún error. Los parámetros pueden ser (además de los comunes):
|
get_objects_count | |
get_objects_from_sql | |
get_objects_iterator | |
get_objects_iterator_from_sql | |
get_objects_sql | |
object_class | |
update_objects | |
make_manager_methods |
Este método fabrica métodos de nombre alternativo en la clase destino para las siguientes operaciones: get_objects, get_objects_iterator, get_objects_count, delete_objects y update_objects. Para ello este método precisa de los siguientes parámetros bien directa o bien indirectamente:
#
# Crea los métodos get_articulos, get_articulos_iterator y contar_articulos
MyManager->make_manager_methods(
methods => {
"articulos" => [ qw(objects iterator) ],
"contar_articulos()" => 'count',
} );
|
make_manager_method_from_sql |
Crea un método de clase que recuperará objetos empleando una instrucción SQL personalizada. El valor retornado por el método será una referencia a una lista de objetos a menos que se utilice el parámetro iterator. Los parámetros son:
MyManager->make_manager_method_from_sql(
method => 'clientes_nuevos',
iterator => 1,
params => [ qw(fecha_alta) ],
sql => <<EOF
select * from personas where fecha_alta >= ?;
EOF
);
my $iterator = MyManager->clientes_nuevos( fecha_alta => '03/03/2013' );
while (my $cliente = $iterator->next) {
...
}
|
Parámetros comunes
La siguiente lista contiene los parámetros comunes para varios de los métodos de clase de la sección anterior:
- prepare_cached => 1
- Ver empleo de caché en preparación de sentencias SQL
- db => Rose::DB
- Objeto derivado de Rose::DB para acceder a la base de datos. Si se omite se creará uno llamando al método init_db de la clase del objeto.
- object_class => CLASS
- Nombre de la clase derivada de Rose::DB que confronta la tabla de la base de datos. Si se omite -dada su importancia- se llama al método de la clase object_class.
Relaciones
Cuando se declaran relaciones entre tablas se crean métodos en la clase declarante que permiten manipular los objetos de la relación.
Los métodos aquí descritos van a usar el nombre relacion como ejemplo para determinar el resultado final. Estos nombres pueden cambiarse empleando el parámetro methods cuando se declara la relación.
__PACKAGE__->meta->setup(
relationships => [
imagenes => {
type => 'one to many',
class => 'Image',
column_map => { id => 'product_id' },
methods => {
count => 'numero_de_imagenes',
add => 'agregar_imagenes',
get => 'reemplazar_imagenes',
},
},
],
...
);
one to one
Métodos posibles y nombres predeterminados | Notas |
---|---|
|
one to many
Métodos posibles y nombres predeterminados | Notas |
---|---|
|
one to many
Métodos posibles y nombres predeterminados | Notas |
---|---|
|
Los interfaces find, iterator y count admiten los parámetros:
|
many to many
Métodos posibles y nombres predeterminados | Notas |
---|---|
|
Los interfaces find, iterator y count admiten los parámetros:
|
Metodología
El uso recomendado consiste en
- Definir una fuente de datos
- Definir los objetos individuales y sus relaciones
- Definir los objetos grupales para manejar conjuntos de objetos individuales
Fuentes de datos
El autor recomienda crear una clase base como fuente de datos para asegurar idéntica configuración y origen de información al resto de las clases derivadas. Esto permite además mantener limpio el espacio de nombres de la biblioteca Rose::DB.
package My::DB;
use base qw(Rose::DB);
use strict;
use warnings;
# Empleamos nuestro propio registro de fuentes de datos
__PACKAGE__->use_private_registry();
# y las declaramos
__PACKAGE__->register_db(
domain => My::DB->default_domain,
type => My::DB->default_type,
driver => 'pg',
database => 'my_db',
host => 'localhost',
username => 'ulises',
password => 'mysecret',
);
1;
Después conviene crear nuestra propia clase base para que los objetos que gestionan las entidades de datos empleen las mismas fuentes de datos y aconsejan usar algo como:
package My::DB::Object;
use base qw(Rose::DB::Object);
use My::DB;
sub init_db { My::DB->new; }
1;
Objetos individuales
Empleando el siguiente esquema de relaciones como ejemplo voy a transcribir más abajo el fuente para declarar los distintos objetos:
package Imagen;
use base qw(My::DB::Object);
__PACKAGE__->meta->setup(
table => 'imagenes',
columns => [
id => { type => 'int', primary_key => 1, not_null => 1 },
author => { type => 'varchar', not_null => 1 },
url => { type => 'varchar' },
imagen => { type => 'varchar', length => 255, not_null => 1 },
],
);
package Marca;
use base qw(My::DB::Object);
__PACKAGE__->meta->setup(
table => 'marcas',
columns => [
id => { type => 'int', primary_key => 1, not_null => 1 },
descripcion => { type => 'varchar', not_null => 1 },
imagen_id => { type => 'int' },
],
foreign_keys => [
imagen => {
class => 'Imagen',
key_columns => { imagen_id => 'id' },
}
],
);
package Producto;
use base qw(My::DB::Object);
__PACKAGE__->meta->setup(
table => 'productos',
columns => [
id => { type => 'int', primary_key => 1, not_null => 1 },
descripcion => { type => 'varchar', not_null => 1 },
marca_id => { type => 'int', not_null => 1 },
],
foreign_keys => [
marca => {
class => 'Marca',
key_columns => { marca_id => 'id' },
},
],
);
1;
Objetos grupales
Este tipo de objetos sirven para manipular listas de otros objetos y se definen derivando una clase de Rose::DB::Object::Manager.
Muchos de ellos precisan de sentencias query para realizar sus operaciones, además de otros parámetros que condicionan su funcionamiento.
package Productos;
use base qw(Rose::DB::Object::Manager);
__PACKAGE__->make_manager_methods(
methods => {
"cargar_marcas()" => 'objects',
"iterar_marcas()" => 'iterator',
"contar_marcas()" => 'count',
"borrar_marcas()" => 'delete',
"modificar_marcas()" => 'update',
},
);
1;
El método de clase make_manager_methods tiene tendencia a definir diversos métodos cuyo nombre deriva de la clase base y la operación a realizar. Estas operaciones son las siguientes:
- objects
- Recupera en una lista un grupo de objetos (y objetos subordinados) utilizando una cláusula de búsqueda con una única instrucción select.
- iterator
- Crea y retorna un objeto iterador sobre un grupo de objetos con los mismos parámetros que el anterior.
- count
- Igual que el tipo objects pero retornando un contador de objetos.
- delete
- Borra un grupo de objetos empleando una claúsula de selección en una única instrucción. El parámetro all es un conmutador para seleccionar el borrado de todos los objetos bajo su ámbito.
- update
- Efectúa modificaciones en atributos de objetos utilizando una claúsula de búsqueda para seleccionarlos.
Recetario
Depuración
Cada módulo parece disponer de una variable llamada Debug que, debidamente activa, provoca la generación de avisos vía warn pero para la que no he encontrado documentación concisa y clara.
En alguno de mis módulos de test empleo lo siguiente:
# Para la biblioteca Rose::DB
use Rose::DB::Object;
$Rose::DB::Object::Debug = 1;
# y para DBI
use DBI;
DBI->trace(2); # incluye parámetros pasados al controlador SQL
La siguiente es una muestra de los mensajes producidos tras un test sencillo de creación de objetos:
... SELECT id, name, idioma, texto FROM descripciones WHERE name = ? - bind params: mrc-001 SELECT id, name, idioma, texto FROM descripciones WHERE id = ? - bind params: 8 INSERT INTO descripciones ( id, name, idioma, texto ) VALUES ( ?, ?, ?, ? ) - bind params: 8, mrc-001, es, Ultracam ...