Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Backbone.js collection upsert?

Tags:

backbone.js

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.

like image 800
Brian Reischl Avatar asked Mar 09 '12 19:03

Brian Reischl


2 Answers

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

Params

  • collection: is a Backbone.Collection
  • collectionJSON: 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.

Example of use

// 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();
like image 55
fguillen Avatar answered Nov 16 '22 00:11

fguillen


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.

like image 29
Brian Reischl Avatar answered Nov 15 '22 22:11

Brian Reischl