Tuesday, September 27, 2011

Adding Backbone.js Models Over Faye

‹prev | My Chain | next›

OK now I am going to go back to working with faye as the persistence layer of my Backbone.js calendar application. For the past two days, I have been on an educational detour in the bowels of Javascript minutiae and Backbone recipes. Good stuff to be sure, but I had started out to explore swapping persistence layers.

When I left off, my add-appointment view was able to open a jQuery dialog to collect the necessary appointment information:
I am already able to publish that information to a faye channel ("/calendars/create"). I even have my backend listening on that channel and creating new appointments when it receives appointments to be created. I can see the HTTP 201 response from CouchDB when that is successful:
Got response: 201 localhost:5984/calendar
I even see the newly added appointment broadcast back on the "/calendars/add" channel:
The only missing piece, then, is listening in the browser for this /calendars/add message and adding it to the appointments Collection.

In the normal RESTful Backbone implementation, the sync() method (either defined directly on the model or the default Backbone.sync()) would call the success() options callback. In my overriden-for-faye sync(), however, I am completely ignoring the entire options object:
    Backbone.sync = function(method, model, options) {
      faye.publish("/calendars/" + method, model);
    }
If I cannot call the success() callback, how does the new model object get created and added to the appointments collection?

For that, I can listen to the /calendars/add channel and manually add the object to the collection:
    faye.subscribe('/calendars/add', function(message) {
      console.log('[/calendars/add]');
      console.log(message);

      calendar.appointments.add(message);
    });
That turns out to be perfectly OK. Even though I am passing in an object literal (hash of attributes), the Backbone collection's add() method does all of the heavy lifting. It turns the attributes into a model, associates that model with the collection and fires the "add" event that can drive the UI to actually add the appointment to the calendar.

Unfortunately, when I give it a try, I am greeted with a missing "description" attribute:
I did send in a description, so where did it get lost? Answer: CouchDB (my storage backend).

When new documents are created in CouchDB, the return value is not the document itself. If you think about it, that would be silly—create a new document and the return value is that document. I just passed that document to you, why are you giving it back?

Instead, when a new document is created, CouchDB replies with a document describing meta information about the newly created document (the id, the rev, etc). Back in the browser, the function listening on the /caledars/add channel has no idea what the original message looked like. All it knows is what the sever broadcasts on /calendars/add (which is currently only meta data):
    faye.subscribe('/calendars/add', function(message) {
      console.log('[/calendars/add]');
      console.log(message);

      calendar.appointments.add(message);
    });
I might be able to create a global variable in the browser code to store pending model creations and try to match them up in the /calendars/add callback. I might be able to do that, but that would be silly.

Instead, the node.js server that is receiving the create request can combine the request with the CouchDB meta information. This combination of data should be sufficient for Backbone to create models:
client.subscribe('/calendars/create', function(message) {
    // ...
    response.on('end', function() {
      var couch_response = JSON.parse(data),
          model = {
            id: couch_response.id,
            rev: couch_response.rev,
            title: message.title,
            description: message.description,
            startDate: message.startDate
          };

      client.publish('/calendars/add', model);
    });
    // ...
  });
With that, I am sending back an object that can be converted into a model. And it works:
After the appointment is returned from the node.js / CouchDB server (via Faye), the /calendars/add callback is able to successfully add it to the appointments collection. From there, all of my pre-Faye UI code kicks in, populating the "#1" appointment on the 26th in the UI.

I am unsure how this will work with update and delete, but I will find out. Tomorrow



Day #146

No comments:

Post a Comment