I'm building an app with Backbone.js. Some of my server-side APIs will return me all the new or changed models since a particular time. Some of those objects may be new to my collection so I can just add them. Others may already exist, in which case I'd like to update the existing model. So basically I'm looking for upsert (update-or-insert) functionality.
This is similar to the {add:true} option that was added in 0.9.0, except that I also want updating.
Is there an easy/known way to do this? It doesn't seem hard to update the code, but I don't want to reinvent a wheel.
I have solved this situation in a generic method:
App.Utils = {
refreshCollection: function( collection, collectionJSON ){
// update/add
_( collectionJSON ).each( function( modelJSON ) {
var model = collection.get( modelJSON.id );
if( model ) {
model.set( modelJSON );
} else {
collection.add( modelJSON );
}
});
// remove
var model_ids_to_keep = _( collectionJSON ).pluck( "id" );
var model_ids = collection.pluck( "id" );
var model_ids_to_remove = _( model_ids ).difference( model_ids_to_keep )
_( model_ids_to_remove ).each( function( model_id_to_remove ){
collection.remove( model_id_to_remove );
});
},
}
collection
: is a Backbone.CollectioncollectionJSON
: is an Array with the models data in Hash style.. typical JSON response.I'm sure it can be optimized, especially the remove part. But I keep it like this for readability due I'm still making tests.
// code simplified and no tested
var VolatileCollection = Backbone.Collection.extend({
// url:
// model:
// so on ...
})
var PersistentCollection = Backbone.Collection.extend({
// url: not needed due this Collection is never synchronized or changed by its own
// change the VolatileCollection instead, all changes will be mirrored to this Collection
// through events
// model: the same
initialize: function( opts ){
this.volatileCollection = opts.volatileCollection;
this.volatileCollection.on( "reset", this.update, this );
this.volatileCollection.on( "change", this.update, this );
}
update: function(){
App.Utils.refreshCollection( this, this.volatileCollection.toJSON() );
}
})
var volatileCollection = new VolatileCollection();
var persistentCollection = new PersistentCollection({ volatileCollection: volatileCollection });
volatileCollection.fetch();
NOTE: This answer was written for Backbone v0.9. Since then Backbone v1.0 has released, which contains the collection.set()
method described here. That will likely be a better solution.
I threw together my own version of this over the weekend. I'll put it up here in case anybody else finds this thread, but I'd still be happy to see any other solutions that might be out there.
I did this by modifying the Backbone.js source, which is not necessarily a great idea, but it was easy. There were two changes, first add this function to the Backbone.Collection prototype:
//**upsert** takes models and does an update-or-insert operation on them
//So models that already exist are updated, and new models are added
upsert: function (models, options) {
var self = this;
options || (options = {});
models = _.isArray(models) ? models.slice() : [models];
var addModels = [];
_.each(models, function (newModel) {
var n = self._prepareModel(newModel, options);
var existingModel = self.get(n.id);
if (existingModel) {
existingModel.set(n, options);
} else {
addModels.push(n);
}
});
if (!_.isEmpty(addModels)) {
self.add(addModels, options);
}
}
Then modify one line in the Backbone.Collection.fetch() function to read:
collection[options.add ? 'add' : (options.upsert ? "upsert" : 'reset')](collection.parse(resp, xhr), options);
This allows you to call fetch({upsert:true})
to get the behavior I was looking for.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With