Sunday, October 2, 2011

Set CouchDB Attributes Before Backbone.js Transport

‹prev | My Chain | next›

I am still not quite done with my conversion to faye as the persistence layer for my calendar Backbone.js application. Most of the mistakes that I have found so far have been of the basic Backbone implementation variety, not the faye-as-persistence-layer variety. Well tonight, I think that I have a legitimate faye persistence problem.

When I update newly created models from my Backbone application, I am getting HTTP 400 responses from CouchDB:
Got response: 400 localhost:5984/calendar/undefined
{ error: 'bad_request', reason: 'Invalid rev format' }
Hrm... Checking the CouchDB logs, I see that I have a least got the PUT part of the update working:
[info] - - 'PUT' /calendar/undefined 400
The PUT is correct, but the "undefined" is a clear indication that whatever I am updating does not have an "id" attribute.

My first instinct is that my overridden Backbone.sync() is not sending the JSON representation of the model, but rather it is sending the model itself:
    var faye = new Faye.Client('/faye');
    Backbone.sync = function(method, model, options) {
      faye.publish("/calendars/" + method, model);
So adding a toJSON() ought to fix the problem:
    var faye = new Faye.Client('/faye');
    Backbone.sync = function(method, model, options) {
      if (model.toJSON) model = model.toJSON();
      faye.publish("/calendars/" + method, model);
Except it has no effect whatsoever. And really, it should not have an effect. If I am always sending a Backbone model object my overridden Backbone.sync() method, then something must already be calling toJSON() on my models. Since Backbone.sync() is sending directly to faye, it must be faye that is calling toJSON(). And in fact it is. Looking through the faye source, for each of the various transports, I see Faye.toJSON:
Faye.Transport.WebSocket = Faye.extend(Faye.Class(Faye.Transport, {
  // ... 
  request: function(messages, timeout) {
    this._timeout = this._timeout || timeout;
    this._messages = this._messages || {};
    Faye.each(messages, function(message) {
      this._messages[] = message;
    }, this);
    this.withSocket(function(socket) { socket.send(Faye.toJSON(messages)) });
  // ...
Ah, so I am lucky that Faye has that toJSON() wrapper. That or faye is just a fabulous choice for a Backbone transport. At the very least, it is not the cause of my woes.

Anyhow, I still have my original problem that some update messages do not have the "_id" or "_rev" attributes needed on the server:
client.subscribe('/calendars/update', function(message) {
  // HTTP request options
  var options = {
    method: 'PUT',
    host: 'localhost',
    port: 5984,
    path: '/calendar/' + message._id,
    headers: {
      'content-type': 'application/json',
      'if-match': message._rev

  // ...
So I finally take the advice of Recipes with Backbone co-author and put mapping of "_id" and "_rev" attributes into the Backbone.sync() method:
    Backbone.sync = function(method, model, options) {
      var message = model.toJSON();
      if (!message._id && message._id =
      if (!message._rev && message.rev) message._rev = message.rev

      faye.publish("/calendars/" + method, message);
I assign an intermediate message variable so that setting attributes does not affect the real model.

With that, I can now update my newly created model as many times as I like:
Best of all, there is nothing but beautiful HTTP 201's from CouchDB:
Got response: 201 localhost:5984/calendar/8b5c80c0211068428272af478400df1e
{ ok: true,
  id: '8b5c80c0211068428272af478400df1e',
  rev: '4-2929cf0e4933a4e32474145eb3e79f02' }
That is a fine stopping point for tonight. I think that I might have resolved all of my faye transport issues (and issues that I noticed after better testing with faye than in the original). I will do some more monkey testing tomorrow and then possibly move on to other areas to explore.

Day #151

No comments:

Post a Comment