4
votes

I am implementing an application using ember.js and couchdb. I choose ember-resource as database access layer because it nicely supports nested JSON documents.

Since couchdb uses the attribute _rev for optimistic locking in every document, this attribute has to be updated in my application after saving the data to the couchdb.

My idea to implement this is to reload the data right after saving to the database and get the new _rev back with the rest of the document.

Here is my code for this:

// Since we use CouchDB, we have to make sure that we invalidate and re-fetch
// every document right after saving it. CouchDB uses an optimistic locking
// scheme based on the attribute "_rev" in the documents, so we reload it in
// order to have the correct _rev value.
didSave:     function() {
    this._super.apply(this, arguments);
    this.forceReload();
},

// reload resource after save is done, expire to make reload really do something
forceReload: function() {
    this.expire();  // Everything OK up to this location
    Ember.run.next(this, function() {
        this.fetch()  // Sub-Document is reset here, and *not* refetched!
            .fail(function(error) {
                App.displayError(error);
            })
            .done(function() {
                App.log("App.Resource.forceReload fetch done, got revision " + self.get('_rev'));
            });
    });
}

This works for most cases, but if i have a nested model, the sub-model is replaced with the old version of the data just before the fetch is executed!

Interestingly enough, the correct (updated) data is stored in the database and the wrong (old) data is in the memory model after the fetch, although the _rev attribut is correct (as well as all attributes of the main object).

Here is a part of my object definition:

App.TaskDefinition = App.Resource.define({

url: App.dbPrefix + 'courseware',

schema: {
    id:          String,
    _rev:        String,
    type:        String,
    name:       String,
    comment:    String,
    task:        {
        type:   'App.Task',
        nested: true
    }
}
});

App.Task = App.Resource.define({

schema: {
    id: String,
    title:       String,
    description: String,

    startImmediate: Boolean,
    holdOnComment: Boolean,
    ..... // other attributes and sub-objects
}
});

Any ideas where the problem might be?

Thank's a lot for any suggestion!

Kind regards, Thomas

1
Hi, did you perhaps already find a solution for the issue? I might go the same way if I don't get this project: github.com/pangratz/ember-couchdb-adapter running correctly.prefabSOFT
In the meantime i found a better solution. See my answer below.Thomas Herrmann

1 Answers

5
votes

I found a much better solution to get the new revision back from the database, without reloading the data after every save.

If save is called in ember-resource with an update option, the response from the db is merged into the resource object. The answer of couchdb to the save request is something like

{"ok":true,"id":"mydocument-123","rev":"10-5f3cb46df301143a966251148f88663d"}

So i just set the _rev property of my objects to the rev value from the db.

My application specific resource class as superclass for all couchdb objects is as follows:

App.Resource = Ember.Resource.extend({

    save: function(options) {
        options = options || {};
        options.update = true; // sets id and rev after saving in ember-resource; rev is copied to _rev in didSave callback!
        this.set('rev', undefined);
        return this._super(options);
    },

    // we get the new revision in the "rev" attribute of the response from the save-request.
    // since we set options.update to true, we get this value in the attribute "rev", which we simply copy to "_rev"
    didSave:     function() {
        this._super.apply(this, arguments);
        this.set('_rev', this.get('rev'));
    }

    .... // other properties and functions
});

The resources are defined like this:

App.TaskExecution = App.Resource.define({

    ....

    schema: {

        id:   String,
        _rev: String,
        rev:  String,
        type: String,
        .....  // other attributes
    }

});

Works pretty well so far....