Introducción
Y por fin llegamos al último tema del tutorial sobre Backbone.js, el cual dedicaremos a ver los mecanismos de persistencia y sincronización con el servidor que nos ofrece la librería. En el tema 2 donde abordamos los Modelos vimos cómo podíamos crear, modificar y borrar modelos en nuestra aplicación, pero en ningún momento dijimos cómo podríamos enviar esa información al servidor para poder almacenarla o procesarla.
Como veremos en este tema, Backbone define un comportamiento por defecto que nos ayudará a mantener sincronizados los datos entre el cliente y el servidor.
Sincronizando modelos con el servidor
A la hora de sincronizar un modelo, Backbone hace uso internamente de la función Backbone.sync() que por defecto realizará una llamada Ajax al servidor apoyándose en el método $.ajax() de jQuery o Zepto.js. Esa llamada Ajax será una solicitud RESTful en la que Backbone serializará todos los atributos del modelo en una cadena JSON para su envío. Si todo es correcto, el servidor devolverá otra cadena JSON con aquellos atributos que hayan sido modificados desde el servidor, y Backbone procederá a actualizar el modelo en el cliente para estar sincronizado con el estado del servidor.
Para que Backbone pueda realizar este proceso, necesita saber la URL de la colección asociada al modelo, ya que el comportamiento por defecto de la librería es mapear las operaciones CRUD (create, read, update, delete) a las siguientes direcciones REST:
create → POST /collection
read → GET /collection[/id]
update → PUT /collection/id
delete → DELETE /collection/id
Es decir, lo primero que debemos hacer es asociar al modelo la URL base de la colección (la parte ‘/collection’ del listado anterior) sobre la que realizar estas operaciones. Esta URL la espera obtener de la propiedad url del modelo, la cual puede ser una cadena o una función. Si no especificamos nada en dicha propiedad, la implementación por defecto recogerá la URL base de la colección a la que pertenece el modelo.
Usuarios = Backbone.Collection.extend({ url: '/usuarios/' }); var usuarios = new Usuarios(); Usuario = Backbone.Model.extend(); var usuario = new Usuario({'nombre': 'Alfonso Marin'}); usuarios.add(usuario); |
Tanto la creación del modelo como su modificación utilizando el método set() no llevan implícita la llamada a Backbone.sync(), así que si queremos sincronizar nuestro modelo tras estas operaciones tendremos que utilizar el método save([attributes],[options]) del modelo. Esta función es parecida al método set() ya que actualizará aquellos atributos que se le pasen como primer parámetro, pero además invocará a Backbone.sync() para que realice la sincronización con el servidor. Además podremos utilizar el segundo parámetro de save() para especificar opciones de la llamada $.ajax() interna que se realiza:
usuario.save({}, { // se genera POST /usuarios - contenido: {nombre:'Alfonso'} success:function(){ alert("Usuario guardado con exito"); } }); |
En este ejemplo la operación save() genera un POST ya que se trata de una operación de creación (‘create’). Backbone interpreta que es una operación de creación siempre que intentemos sincronizar una instancia de modelo que no contenga ningún valor en su atributo identificador, que por defecto es el atributo id o aquel que hayamos designado como identificador a través del atributo idAttribute cuando definimos el modelo.
Si el servidor devuelve algún objeto JSON éste será utilizado para actualizar el modelo. Esto es típico que suceda precisamente en las operaciones de creación, donde el servidor nos devolverá el identificador del nuevo elemento creado.
usuario.save({}, { // se genera POST /usuarios - contenido: {nombre:'Alfonso'} success:function(){ // Suponiendo que el servidor ha devuelto el objeto {"id": 1} alert(usuario.id); // imprime 1 } }); |
En este ejemplo hemos supuesto que el servidor devuelve un objeto JSON con el identificador asignado a la nueva instancia sincronizada, así que Backbone aplica dicho objeto sobre la instancia del modelo.
Una vez que el modelo ya tiene su identificador, cualquier operación posterior sobre la instancia de modelo provocará una operación de actualización (‘update’) sobre el servidor:
usuario.save({apellidos: 'Marin Marin'}); // se genera PUT /usuarios/1 - contenido {id: 1, nombre: 'Alfonso', apellidos: 'Marin Marin'} |
Como muestra el ejemplo, la operación de actualización envía todo el contenido del objeto al servidor a través de un PUT a la URL específica del modelo.
Aparte del método save() existen otros dos métodos de modelo que realizan sincronizaciones con el servidor: fetch([options]) y destroy([options]). El método fetch() realiza una operación de lectura (‘read’) y sirve para refrescar los datos del modelo a partir de la copia existente en el servidor:
var usuario = new Usuario({id:1}); // Creamos una instancia inicializando el ID del objeto que queremos recuperar usuarios.add(usuario); // Añadimos la instancia a la colección para que Backbone sepa la url base de la colección usuario.fetch({ // se genera GET /usuarios/1 success:function(){ alert(JSON.stringfy(usuario.attributes)); // imprime {"id":1, "nombre": "Alfonso", "apellidos": "Marin Marin"} } }); |
Por otra parte sirve para generar una operación de borrado (‘delete’) y eliminar la instancia del objeto del servidor :
var usuario = new Usuario({id:1}); // Creamos una instancia inicializando el ID del objeto que queremos recuperar usuarios.add(usuario); // Añadimos la instancia a la colección para que Backbone sepa la url base de la colección usuario.destroy(); // se genera DELETE /usuarios/1 |
Al igual que ocurría con save() ambas funciones permiten definir como parámetro las opciones a utilizar en la llamada Ajax.
Como hemos visto en todos estos ejemplos, siempre hemos introducido la instancia del modelo que queríamos sincronizar dentro de una colección, como mecanismo para que Backbone pudiese recuperar la URL base de la colección y así formar las operaciones REST para cada tipo de operación CRUD. Si por cualquier motivo deseamos trabajar con instancias de modelo sin tener que asociarlas a ninguna colección, podemos hacer uso del atributo urlRoot en la definición del modelo, donde definiremos precisamente la ruta base:
Usuario = Backbone.Model.extend({ urlRoot: '/usuarios/' }); var usuario = new Usuario({id: 1}); usuario.fetch(); // genera GET /usuarios/1 |
Como vemos, definiendo urlRoot no será necesario asociar a un modelo a ninguna colección para poder sincronizarlo con el servidor, obteniendo exactamente le mismo comportamiento que si lo estuviera.
Por último, si queremos que un modelo en particular no tenga este comportamiento por defecto y queremos asociarlo a una URL final para todas las operaciones del modelo, podremos definir directamente la propiedad url en la definición del modelo:
Usuario = new Backbone.Model.extend({ url: '/usuario'; }); var usuario = new Usuario({id: 1}); usuario.fetch({ // genera GET /usuario success: function(){ usuario.save({'nombre': 'Antonio'}, { // genera PUT /usuario success:function(){ usuario.destroy(); // genera DELETE /usuario } }); } }); |
De esta forma Backbone siempre realizará las operaciones CRUD sobre la misma URL fija, sin añadir o quitar el id, esté o no esté dentro de una colección, y tenga o no tenga definido algún valor en urlRoot.
Colecciones y sincronización
Como hemos visto en el apartado anterior el concepto de sincronización de modelos está muy ligado al concepto de colección debido a la propia naturaleza de las interfaces REST. En los ejemplos hemos visto que definiendo la URL base de la colección los modelos incorporados a ella serán capaces de realizar las operaciones de sincronización.
Aparte de los métodos de sincronización existentes en los modelos, las colecciones disponen de un método capaz de recuperar una colección entera desde el servidor: el método fetch([options]):
Usuarios = Backbone.Collection.extend({ url: '/usuarios' }); var usuarios = new Usuarios(); usuarios.fetch({ // genera GET /usuarios success: function(){ alert('Recuperados ' + usuarios.length + ' usuarios'); } }); |
Este método realiza una operación GET sobre la URL base de la colección, y espera recibir un objeto JSON consistente en un array de instancias de modelos, que se incluirán en la colección. Esta operación generará el evento Backbone refresh.
A la hora de añadir modelos a la colección hemos estado haciendo uso de la función add(), y posteriormente de la función save() para realizar la sincronización con el servidor. También podremos utilizar la función create() que nos ofrece el objeto colección, la cual realiza estas dos operaciones de forma implícita, es decir, crea el modelo, lo envía al servidor para su sincronización y validación, y posteriormente lo incluye a la colección.
usuarios.create({'nombre':'Alfonso Marin'},{ success: function(serverModel){ alert(serverModel.id); } } |
Personalizando la sincronización
Como se comentó al principio, cada operación de sincronización realmente la ejecuta la función Backbone.sync(), cuya implementación por defecto sincroniza los datos con el servidor a través de llamadas Ajax. Si lo deseamos, podemos remplazar esta función y cambiar a otra estrategia de sincronización, como por ejemplo utilizar alguna de las funcionalidades HTML5 como WebSockets o Local Storage. Por ejemplo, podemos sobrescribir esta función para que simplemente muestre por consola las operaciones que se van realizando:
Backbone.sync = function(method, model, options) { console.log(method, model, options); options.success(model); }; |
Como vemos en el ejemplo, la función Backbone.sync() espera 3 parámetros:
- method: el método CRUD que se va a realizar (‘create’, ‘read’, ‘update’, or ‘delete’)
- model: El modelo a salvar (o la colección a ser leída)
- options: Las opciones de la solicitud, donde se incluirán funciones callback de success y failure
Lo único que espera Backbone es que se invoquen las funciones callback options.success() u options.error() en los casos oportunos.
Sobrescribir la función Backbone.sync() nos permitirá cambiar la estrategia de sincronización de forma global, pero también es posible sobrescribir esta función únicamente en un determinado modelo o colección:
Usuarios.prototype.sync = function(method, model, options){ /* ... */ } |
Un buen ejemplo de personalización de Backbone.sync() es este adaptador para Local Storage. No dejes de echarle un vistazo.
Conclusión
Con este tema doy por finalizado el tutorial de Backbone. Como dije al principio intentaría realizar una aplicación de ejemplo, aunque ya hay bastante material dentro de la sección de tutoriales de Backbone, aunque todos ellos en inglés. Si saco algo de tiempo la realizaré.
Intentaré mantener actualizado este tutorial con las nuevas funcionalidades que se vayan incorporando en sucesivas versiones de la librería.
German says
Esperando con ansias el tutorial de la aplicación, saludos!! un abrazo.
Alberto Blas says
Muy buen tutorial. Me ha ayudado mucho.
¡Gracias!
Iván Gonzálezº says
Me ha encantado encontrar un tutorial sencillo, que toca todo lo necesario y además en plano castellano. Muchas gracias por tomarte la molestia de haberlo escrito.
christian says
excelente tutorial, si se que existen muchos por la red, pero entendible y en castellano me quedo con este, espero con ansias el tutorial de la aplicacion, para asentar los conceptos y ver una aplicacion practica, GRACIAS por el tutorial!!!!!!!!
Ivo rojas says
excelente!
Emiliano Jankowski says
Muy buen post!!!
pbdf says
Hola, el tuto es verdaderamente bueno, no heches esto a saco roto, espero veamos pronto una aplicación fastidiosamente sencilla.
Ale Prieto says
¡muy bueno!
¡muchas gracias!
Beto says
Me lei el tutorial entero. GrAcias!
Matías Damonte says
Muy buen tuto
Xavi Aznar says
Gracias por el tutorial; me lo llevo de vacaciones en el portátil para aprender un poco más sobre este framework.
Joaquín Velasco says
Muy buen tutorial, muchas gracias. Espero con ansia la aplicación completa de ejemplo.
Javier Perez says
Gracias por arrojar luz en el camino