• Skip to primary navigation
  • Skip to content
  • Skip to footer
  • Sobre mí
  • Servicios
  • Blog
  • Contacto

Tutorial de Backbone.js – VI. Sincronización y persistencia

20 enero, 2012 by alfonso 13 Comments

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);

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");
    }
});

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
    }
});

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'}

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"}
    }
});

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

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

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
            }
        });
    }
});

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');
    }
});

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);
  }
}

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);
};

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){ /* ... */ }

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.

 

  • Facebook
  • Twitter
  • Google+

Desarrollo Web backbonejs,  tutorial

Reader Interactions

Comments

  1. German says

    14 marzo, 2012 at 12:29 am

    Esperando con ansias el tutorial de la aplicación, saludos!! un abrazo.

    Responder
  2. Alberto Blas says

    17 julio, 2012 at 4:51 am

    Muy buen tutorial. Me ha ayudado mucho.

    ¡Gracias!

    Responder
  3. Iván Gonzálezº says

    30 julio, 2012 at 6:20 pm

    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.

    Responder
  4. christian says

    10 noviembre, 2012 at 8:15 pm

    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!!!!!!!!

    Responder
  5. Ivo rojas says

    2 diciembre, 2012 at 9:53 pm

    excelente!

    Responder
  6. Emiliano Jankowski says

    21 febrero, 2013 at 2:48 pm

    Muy buen post!!!

    Responder
  7. pbdf says

    1 marzo, 2013 at 9:24 pm

    Hola, el tuto es verdaderamente bueno, no heches esto a saco roto, espero veamos pronto una aplicación fastidiosamente sencilla.

    Responder
  8. Ale Prieto says

    3 marzo, 2013 at 3:44 pm

    ¡muy bueno!

    ¡muchas gracias!

    Responder
  9. Beto says

    24 marzo, 2013 at 2:49 am

    Me lei el tutorial entero. GrAcias!

    Responder
  10. Matías Damonte says

    28 marzo, 2013 at 8:12 pm

    Muy buen tuto

    Responder
  11. Xavi Aznar says

    26 julio, 2013 at 9:52 am

    Gracias por el tutorial; me lo llevo de vacaciones en el portátil para aprender un poco más sobre este framework.

    Responder
  12. Joaquín Velasco says

    21 octubre, 2013 at 10:22 am

    Muy buen tutorial, muchas gracias. Espero con ansia la aplicación completa de ejemplo.

    Responder
  13. Javier Perez says

    10 noviembre, 2016 at 9:10 am

    Gracias por arrojar luz en el camino

    Responder

Deja una respuesta Cancelar la respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Footer

  • LinkedIn
  • RSS
  • Twitter
  • Aviso legal
  • Política de privacidad
  • Política de cookies
  • Contacto

Copyright ©2025 · Alfonso Marín Marín - Todos los derechos reservados ·

Usamos cookies para asegurar que te damos la mejor experiencia en nuestra web. Si continúas usando este sitio, asumiremos que estás de acuerdo con ello.Aceptar