Este es uno de los capítulos del tutorial Aplicaciones nativas en JavaScript con GJS. Encontrarás los enlaces a todos los de capítulos, al final de este artículo.
Tienes muchas opciones para guardar información desde tu aplicación nativa implementada con JavaScript y Gtk+. Pero cuando tu aplicación va creciendo en tamaño y en uso tienes, que ir pensando en gestionar esa información de forma inteligente. Desde luego que lo más cómodo es utilizar bases de datos, pero ¿como utilizar bases de datos con JavaScript y GJS? Por supuesto, que siempre me estoy refiriendo a esta aplicación que estamos implementando utilizando JavaScript y GJS.
La solución viene de la mano de GDA, Acceso a Datos de GNOME. Se trata de una librería multipropósito cuyo objetivo es el de proporcionar acceso universal a diferentes tipos de fuentes de datos. Y no solo se trata de dar acceso a bases de datos relacionales típicas, sino también a cualquier otro tipo de fuente de datos que te puedas imaginar, desde un servidor de correo electrónico hasta un directorio de LDAP, por mencionarte algunas.
Bases de datos con JavaScript y GJS
A la práctica
Con el objeto de que este capítulo del tutorial no sea tan árido, lo acompañaré de principio a fin de un ejemplo práctico. En concreto, se trata de una sencilla base de datos para asignar a cada número de los Iguales su correspondiente apodo. Si no sabes a que me refiero, te remito a este artículo de La vanguardia.
Por no hacer esto interminable, indicarte los cinco primeros,
0
La Muerte1
El Galán2
El Sol3
El Chiqué4
La Cama
Así, voy a crear una clase simple llamada Igual
que tenga la propiedad numero y su correspondiente apodo,
var Igual =
class Igual{
constructor(numero, apodo){
this._numero = numero;
this._apodo = apodo;
}
get numero(){
return this._numero;
}
set numero(numero){
this._numero = numero;
}
get apodo(){
return this._apodo;
}
set apodo(apodo){
this._apodo = apodo;
}
toString(){
return this._igual + ": " + this._apodo;
}
equals(other){
return ((other instanceof Igual) &&
other._numero == this._numero &&
other._apodo == this._apodo);
}
}
El último de los métodos que he definido, toString
, te permite convertir un objeto de la nueva clase que has definido, Igual
, en una cadena de texto correctamente formateada, de forma que el usuario sabe con lo que está tratando.
Y un par de ejemplos para demostrar el funcionamiento,
let igual = new Igual(0, "La Muerte");
log(igual);
"""
igual.igual = "1";
igual.apodo = "El Galán";
log(igual);
Lo mínimo indispensable
Para definir nuestra base de datos que contenga los iguales, son necesarios unos mínimos. Es decir, necesitamos importar un par de módulos que nos van a permitir interactuar con bases de datos y además poder borrar el archivo de la base de datos cuando así lo necesitemos. Para esto,
const {Gda, Gio} = imports.gi;
imports.searchPath.unshift(".");
var Igual = imports.iguales.Igual;
Con Gda
gestionaremos nuestra base de datos en SQLite, mientras que con Gio
, podremos eliminar el archivo que contiene la base de datos. Por otra parte, también nos importamos la clase que has definido anteriormente Igual
. Para esto utilizamos el imports.searchPath.unshift(".")
.
El constructor de nuestra base de datos
El primer paso es crear el constructor de nuestra clase de base de datos que nos permitirá lidiar con ella. Este constructor, lo vamos a definir de la siguiente forma,
var Database =
class Database{
constructor(filename){
this._filename = filename;
this.connection = new Gda.Connection({
provider: Gda.Config.get_provider("SQLite"),
cnc_string: `DB_NAME=${filename}`
});
this.init();
}
}
Aquí has definido el nombre del archivo que contiene la base de datos, aunque en este caso no lleva extensión, porque se lo añade por defecto, .db
. Esto lo tienes que tener en cuenta a la hora de borrar este archivo.
Por otro lado, también defines la conexión, indicando que es del tipo SQLite
y la cadena de conexión. En este caso he omitido la ruta, pero, en tu aplicación deberías tenerlo en cuenta. Así, la cadena de conexión sería,
cnc_string: `DB_DIR=${directorio};DB_NAME=${filename}`
Recuerda que para la cadena de conexión indicada, el archivo que contiene la base de datos no tiene la extensión. Si no te encontrarás que tu base de datos tiene nombre como base_de_datos.db.db
.
Inicializando la base de datos
El siguiente paso es inicializar la base de datos. Al menos se trata de crear una sencilla tabla donde estarán contenidos nuestros iguales. Esta tabla, tendrá un índice, el número del igual y el apodo del igual. Para inicializar la base de datos, al igual que en general, para cualquier otra operación, tienes que conectarte a la base de datos, y una vez realizada todas las operaciones que consideres, desconectarte. Así, este método de inicialización será similar a este,
init(){
this.connection.open();
let sql = "CREATE TABLE IF NOT EXISTS iguales (id INTEGER PRIMARY KEY, numero INTEGER, apodo VARCHAR);"
this.connection.execute_non_select_command(sql);
this.connection.close();
}
Evitar duplicados
Dado que en este caso no vas a querer tener dos registros con el mismo número, tienes dos opciones para evitar este inconveniente. O bien, lo defines añadiendo una restricción a la base de datos, o bien, cada vez que añades un nuevo elemento compruebas que no existe.
En el primer caso, es sencillo, tan solo tienes que modificar la consulta de creación de la tabla, añadiendo UNIQUE(numero)
. Incluso no estaría de mas que añadieras una segunda restricción para que tampoco se pueda repetir el apodo, con UNIQUE(apodo)
. Así, la sentencia de creación te quedaría algo como,
let sql = "CREATE TABLE IF NOT EXISTS iguales (id INTEGER PRIMARY KEY, numero INTEGER, apodo VARCHAR, UNIQUE(numero), UNIQUE(apodo));"
En el segundo caso, tienes que utilizar algo similar a lo que te muestro a continuación,
exists(igual){
if(igual instanceof Igual){
igual = igual.numero;
}
let iguales = this.searchByIgual(igual);
return iguales.length > 0;
}
Añadir iguales
Una vez creada nuestra base de datos, el siguiente paso, es añadir registros para que la base de datos tenga sentido. Ten en cuenta lo mencionado en el punto anterior, y dependiendo la opción que hayas escogido, tendrás que tener en cuenta unas cuestiones u otras. Así, el código podría ser similar al que te muestro a continuación,
insert(unigual){
this.connection.open();
let numero = unigual.numero;
let apodo = unigual.apodo;
let sql = `INSERT INTO iguales (numero, apodo) values('${numero}', '${apodo}')`;
let row = this.connection.execute_non_select_command(sql);
this.connection.close();
return row > 0;
}
Actualizar iguales
Otra de las opciones básicas que seguro tendrás que hacer en uno u otro momento al tratar con una base de datos es actualizarlo. Está claro, que en este ejemplo, no tiene mucho sentido, porque a estas alturas no creo que los iguales vayan a cambiar. Sin embargo, en cualquier otro caso es una opción que tienes que considerar,
update(unigual){
this.connection.open();
let numero_value = unigual.numero;
let apodo_value = unigual.apodo;
let sql = `UPDATE iguales SET apodo='${apodo_value}' WHERE numero='${numero_value}'`;
let row = this.connection.execute_non_select_command(sql);
this.connection.close();
return row > 0;
}
Aunque en el caso de que te equivoques a la hora de introducir datos en la base de datos, y quieras rectificar uno o varios, es una posibilidad que siempre está bien tener a mano.
Borrar elementos de la base de datos
Otra de las acciones que en el ejemplo de los iguales no tendrás que utilizar, al menos a priori, es la de borrar elementos de la base de datos. Pero de nuevo, es una herramienta, que tienes que tener en tu caja de herramientas para el caso de que la necesites utilizar.
Una observación importante, es que tienes que tener cuidado con los posibles duplicados, porque en este caso, no vas a modificar el número, pero si el apodo, con lo que te puedes encontrar con dos iguales, con diferente número, pero igual apodo, con lo que deberías considerar lanzar una excepción o, sea como fuere, tratar el posible error.
delete(igual){
if(igual instanceof Igual){
igual = igual.numero;
}
this.connection.open();
let sql = `DELETE FROM iguales WHERE numero='${igual}'`;
let row = this.connection.execute_non_select_command(sql);
this.connection.close();
return row > 0;
}
Búsquedas
Si las operaciones de actualizar y borrar es muy improbable que la tengas que utilizar en este ejemplo, aunque seguro que la necesitarás en otras situaciones, lo que es indudable que si necesitarás son las de búsqueda. En este caso te propongo dos opciones posibles, una primera que es buscar por el número del igual,
searchByIgual(igual){
if(igual instanceof Igual){
igual = igual.numero;
}
let iguales = [];
this.connection.open();
let sql = `SELECT numero, apodo FROM iguales WHERE numero='${igual}'`;
let dataModel = this.connection.execute_select_command(sql);
let iter = dataModel.create_iter();
while(iter.move_next()){
let new_igual = new Igual(iter.get_value_at(0),
iter.get_value_at(1));
iguales.push(new_igual);
}
this.connection.close();
return iguales;
}
Si te fijas aquí todo el trabajo lo realiza un puntero, iter
, que obtienes utilizando dataModel.create_iter()
. Te vas moviendo a través de todos los registros obtenido con iter.move_next()
, obteniendo los valores de cada una de las columnas con iter.get_value_at(<numero de la columna>)
.
Si en lugar de querer obtener todos los valores buscando por el número lo quisieras hacer por el apodo, básicamente la consulta que tendrías que lanzar sería la siguiente,
let sql = `SELECT igual, apodo FROM iguales WHERE apodo='${apodo}'`;
Obteniendo todos los registros
En el caso de los iguales, no tiene mucho problema porque es una cantidad relativamente pequeño, y por esto implemento este método. Sin embargo, en otras búsquedas te tienes que replantear el paginar los resultados, porque de otra forma, el usuario tendrá una mala experiencia, porque tardará excesivamente mucho en cargar los valores.
De una forma muy similar a la que te he mostrado en el apartado anterior, aquí, para obtener todos los valores lo puedes hacer de la siguiente forma,
getAll(){
let iguales = [];
this.connection.open();
let sql = "SELECT numero, apodo FROM iguales";
let dataModel = this.connection.execute_select_command(sql);
let iter = dataModel.create_iter();
while(iter.move_next()){
let new_igual = new Igual(iter.get_value_at(0),
iter.get_value_at(1));
iguales.push(new_igual);
}
this.connection.close();
return iguales;
}
Y de la misma forma, es posible que quieras eliminar todos los registros de la base de datos. En este caso la operación es tan sencilla como lo que te muestro a continuación,
removeAll(){
let sql = "DELETE FROM iguales";
this.connection.open();
let row = this.connection.execute_non_select_command(sql);
this.connection.close();
}
SQLite
Desde luego, como estás trabajando con una base de datos SQLite, puedes leer o modificar la misma de forma sencilla, para hacer operaciones de mantenimiento o simplemente por hacer consultas. Sin embargo, no pierdas el foco a que aunque me he centrado en SQLite, esto es mas general y es completamente agnóstico, lo que te permite trabajar con bases de datos con JavaScript y GJS con independencia de la base de datos.
En este sentido, te recomiendo que le des una lectura a un artículo que publiqué hace un tiempo que seguro te será de utilidad sobre como trabajar con SQLite desde Bash.
Conclusiones
Como ves trabajar con bases de datos con JavaScript y GJS, y en concreto con una base de datos SQLite es realmente sencillo. Pero lo mejor de todo, es que esto mismo es extrapolable a otras bases de datos, porque como he comentado en la introducción, GDA es una librería multipropósito que te permitirá acceder a diferentes orígenes. Simplemente fantástico.
Más información,
Imagen de portada de Campaign Creators en Unsplash
seria bueno conectar gjs a una api rest
Excelente tutorial. LLevo muchos años con Linux y este tema (GJS) no lo vi en ningún lado. Todo un descubrimiento. Muchas gracias.
Muchas gracias. Espero que lo disfrutes. Cualquier idea, sugerencia o comentario, es bienvenido.