12 mayo, 2009

TaffyDB

Siguiendo el debate sobre el mecanismo a introducir en los navegadores para implementar bases de datos locales, describo aquí las características y el funcionamiento de TaffyDB: TaffyDB es una librería JavaScript open source que actua como una delgada capa para aplicaciones web 2.0 y AJaX. Sus características son:

  • Tamaño de 10KB
  • Sintaxis simple centrada en JavaScript
  • Rápida
  • Fácil de incluir en cualquier aplicación web
  • Compatible con las librerías más comunes: YUI, jQuery, MooTools, Dojo, Prototype, EXT, ...
  • Interfície CRUD (Crear,Leer,Actualizar,Eliminar)
  • Ordenación
  • Bucles
  • Consultas avanzadas

Introducción rápida

Creando colecciones

Para crear una colección Taffy hay que pasar un array de objetos similares como el que devuelve un servicio web JSON, por ejemplo cuatro amigos:
var amigos = new TAFFY(
[
{nombre:"Pepe",
  genero:"H",
  casado:"No",
  edad:25,
  ciudad:"MAD",
  comidas_preferidas:["pizza","tacos"]},
 {nombre:"Eva",
  genero:"M",
  casado:"No",
  edad:29,
  ciudad:"BCN",
  comidas_preferidas:["ensalada","palitos de queso"]},
 {nombre:"Paco",
  genero:"H",
  casado:"No",
  edad:29,
  ciudad:"VLC",
  comidas_preferidas:["pizza","hamburguesas","BLTs"]},
 {nombre:"Sara",
  genero:"M",
  casado:"No",
  edad:21,
  ciudad:"ZAZ",
  comidas_preferidas:["pizza","sushi"]}
  ]
)
También acepta una cadena de texto JSON por evaluar. Siempre devolverá una colección de métodos para trabajar sobre el conjunto recibido.

Encontrar

Para ver los amigos que tienen más de 22 años:
amigos.find({edad:{greaterthan:22}});
Para realizar una consulta se debe llamar al método find con un objeto como parámetro para indicar las condiciones de filtrado a aplicar. Es similar a la cláusula where de SQL. Devuelve un array de índices a los objetos que cumplan las condiciones. Para encontrar a los amigos que vivan en Valencia, Madrid o Zaragoza:
amigos.find({ciudad:["VLC","MAD","ZAZ"]});
Que sería similar al operador IN de SQL.

Actualizar

Cómo actualizar la colección para reflejar que Pepe ahora vive en Bilbao y se ha casado:
amigos.update(
 {
 ciudad:"BIO",
 casado:"Yes"
 },
 {
 nombre:"Pepe"
 }
);
El método update recibe un objeto con los datos que hay que cambiar y otro objeto (cláusula WHERE) con las condiciones de filtrado para encontrar los objetos sobre los que hay que aplicar los cambios. También es posible indicar el índice del objeto a modificar o directamente el resultado de una búsqueda (si no se indica un segundo parámetro, las modificaciones afectarán a todos los objetos de la colección):
amigos.update({ciudad:"BIO",casado:"Yes"},1);
amigos.update(
 {
 ciudad:"BIO",
 casado:"Yes"
 },
 amigos.find(
  {nombre:"Pepe"}
  )
 );

Insertar

El método insert acepta un objeto a insertar o un array de objetos:
amigos.insert(
 {nombre:"Brian",
 genero:"H",
 casado:"No",
 edad:52,
 ciudad:"IBZ",
 comidas_preferidas:["fruta","chuleta"]
 });

Eliminar

El método para eliminar registros se llama remove dado que delete es una palabra reservada. Acepta como parámetro un objeto con las condiciones de filtrado para seleccionar los objetos a borrar:
amigos.remove({nombre:"Brian"});

Ordenar

Para ordenar hay que indicar un array con los campos de ordenación, indicando el nombre del campo si es ascendente o con la expresión {"campo":"desc"} si es descendente. Por ejemplo, para ordenar por edad ascendente y por nombre alfabético pero descendente:
amigos.orderBy(["edad",{"nombre":"desc"}]);
También es posible ordenar de forma lógica indicando como parámetro "logical" o "logicaldesc" de la misma forma como se indica "desc" para los descendentes. El método orderBy también acepta un segundo parámetro opcional con una función JavaScript como la que acepta la array.sort().

ForEach

Es un método que permite aplicar una función a cada elemento de una colección:
amigos.forEach(function (f,n) {alert(f.nombre)});
El primer parámetro de la función es el objeto y el segundo el índice del objeto dentro de la colección. Opcionalmente se pueden indicar condicionantes de filtrado para reducir el conjunto de objetos sobre el que se aplica la función:
amigos.forEach(
 function (f,n) {alert(f.nombre);},
 {comidas_preferidas:{has:"pizza"}}
);
Es posible modificar un registro, devolviendo una copia del objeto recibido desde la función. Si no se devuelve nada, el registro no se modifica. Por ejemplo, para incrementar la edad de todos los amigos:
amigos.forEach(
    function (f,n) {f.edad = f.edad+1; return f;}
);

Get/First/Last

Para obtener un conjunto de objetos que cumplan una condición de filtrado, se usará get(); first() para obtener el primer objeto que cumpla la/s condición/es, y last() para obtener el último. Si no se especifica el parámetro, las mismas funciones devolverán todos los objetos, o el primero y último de la base de datos.

amigos.get({nombre:"Pepe"});
amigos.first({nombre:"Pepe"});
amigos.last({nombre:"Pepe"});

Stringify

Funciona como get() pero devuelve el resultado como una cadena de texto JSON preparada para ser usada en servicios web / AJaX.

Templates (patrones)

Se pueden usar patrones para añadir valores por defecto a una colección y minimizar el código escrito. Un patrón es un objeto que será usado como base para nuevas inserciones. A menos que el nuevo registro sobreescriba los valores indicados en el patrón, TaffyDB usara los valores del patrón para rellenar el registro.

Para preparar un patrón llamar a collection.config.set("template",{}). Para eliminarlo se llamará a collection.config.set("template",null). Un patrón se aplicará automáticamente a cada nuevo registro de la colección.

Este ejemplo añade un patrón a la colección de amigos y establece unos valores por defecto para el teléfono y el correo electrónico. Cualquier amigo que ya tuviese definidos dichos valores no se vería afectado por el patrón:

amigos.config.set("template",
 {email:"none",
 telefono:"none"}
);
También se puede usar collection.applyTemplate para aplicar un patrón a un subconjunto de los registros. El patrón se aplicará en este caso a los registros actuales, pero no a las futuras inserciones o cambios aunque cumplan la/s condición/es indicadas:

amigos.applyTemplate(
 {"conyugue":"desconocida"},
 {casado:"Yes"}
);
Nota: es posible recuperar el patrón impuesto a una colección mediante collection.config.get("template").

Eventos

Hay tres eventos que pueden usarse:
  • onInsert(nuevoObj) - se dispara cada vez que se inserta un nuevo registro.
  • onUpdate(modificadoObj,originalObj) - se dispara cada vez que un registro es actualizado.
  • onRemove(eliminadoObj) - se dispara cuando un nuevo registro es eliminado.

Ejemplo:
amigos.onRemove = function (r) {
 alert(r.nombre + " ha sido eliminado");
};
amigos.remove();

Consultas avanzadas

Ya que casi todos los métodos de TaffyDB incluyen condiciones de filtrado, conviene comprender sus posibilidades:

Usando el método de búsqueda detallado arriba, es posible pasar varios filtros para reducir los resultados:

amigos.find({ciudad:["VLC","BCN","MAD"],
  edad:{greaterthan:22}}); //mayorque
Aquí se recibirán los amigos de las tres ciudades que tengan más de 22 años. Agregar filtros a una consulta es equivalente a usar un AND en un WHERE de SQL.

Nota: es posible invertir la lógica de filtrado añadiendo el signo ! antes del nombre, que sería similar al operador != en el SQL estándar. Esto se aplica a todos los tipos de filtrado.
Ejemplo (encontrar amigos que no sean de Madrid):
amigos.find({ciudad:{"!is":"MAD"});
Hay 17 formas de filtrar una colección:
  • equal (default) (equivalente: is) // Colección que coincida con un texto o número indicado.
  • startswith (equivalente: starts) // Colección que empieza por el texto indicado.
  • endswith (equivalente: ends) // Colección que termina con el texto indicado.
  • greaterthan (equivalente: gt) // Colección mayor que el número indicado.
  • lessthan (equivalente: lt) // Colección menor que el número indicado.
  • has // Colección que contiene el objeto, texto, o clave indicado.
  • hasAll // Colección que contiene todos los objetos, textos o claves definidas en el array indicado.
  • regexppass (equivalente: regex) // Colección que cumple una expresión regular.
  • like // Colección que incluye el texto indicado.
  • notlike // Colección que no incluye el texto indicado.
  • isSameObject // El valor del objeto colección coincide con el objeto indicado.
  • isSameArray // El valor de array de la colección coincide con el array indicado.
  • length // La longitud del valor de la colección coincide con el número indicado (leer a continuación).

Usando los filtros de longitud

Usando el filtro de longitud es posible encontrar cadenas y arrays basándose en su longitud. Las forma simple es con una coincidencia de uno a uno:

friends.find({state:{length:15});

La otra forma es utilizar uno de los métodos de arriba. Es una buena forma de encontrar longitudes por encima o por debajo de un valor:

friends.find({state:{length:{gt:10}});
Consultas basadas en tipos

La version 1.4 de TaffyDB introdujo un número de métodos para filtrar que se basan en el tipo. Ya que TaffyDB permite almacenar cualquier tipo de datos en cualquier columna, los métodos pueden usarse para filtrar por una columna multi-tipo. Las opciones para filtrar por tipo son:

  • isString // La colección es una cadena.
  • isNumber // La colección es un número.
  • isArray // La colección es un array.
  • isObject // La colección es un objeto.
  • isBoolean // La colección es a boolean (true/false).
  • isFunction // La colección es function.
  • isNull // La colección es null.
  • isUndefined // La colección es "undefined".
  • isNumeric // La colección contiene sólo números.
  • isTAFFY // La columna de colección es una colección de TaffyDB.

Para usar estos filtros se necesitará pasar un valor "true" o "false" contra el que comparar:

amigos.find({ciudad:{isArray:true}});
Construyendo consultas eficientes

Además de poder filtrar de distintas formas y combinar los filtros para llevar a cabo consultas más complejas, también se pueden optimizar las consultas pasando al método de consulta una lista con los índices contra los que ejecutar la consulta en lugar de tener que hacerlo sobre la colección entera:

amigos.find({ciudad:["MAD","BCN","VLC"],
  edad:{lt:50}},
  [0,2]);
Cada llamada al método de consulta requerirá que cada registro de la colección sea comprobado al menos una vez. Para consultas complejas y colecciones grandes, esto puede requerir mucho trabajo con bucles. TaffyDB reduce automáticamente los resultados conforme va filtrando (con el segundo filtro siendo aplicado sólo a aquellos registros que han pasado el primero, y así sucesivamente). Pero a veces eso no es suficiente. En el ejemplo anterior ya limitamos los resultados indicando un array de índices. Esta es una forma de evitar que TaffyDB recorra la colección completa si ya se conocen los registros sobre los que se va a actuar.

Publicar un comentario en la entrada

Últimos links en indiza.com