Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

merge backbone collection with server response

TL;DR: If I'm polling the entire collection of models from the server, how can I merge the changed attributes into each model and add/remove added/removed models from the collection?

In my backbone app, I am polling for the entire collection of models. I have a Backbone.Collection that I am basically calling reset on each time I get the array of models, so:

myCollection.reset(server_response);

The only problem with this is that it gets rid of the old models, kind of eliminating the benefit of events on a model. This is reset's purpose of course, but what I want to do is only modify the changed attributes of the models, and remove models not in the response, and add models that were in the response but not the collection.

Essentially I want a sort of merge of the data.

For models that are in the response and in the collection already, I believe I can just do model.set(attributes) and it takes care of seting only the ones that actually changed, triggering the change events in the process. This is great.

But how do I handle the cases where the models were in the response but not in the collection already, and vice versa, not in the response but in the collection?

My Proposed Solution

I don't know if backbone already has a way of doing this, and I may be overcomplicating which is why I'm asking, but I was thinking then of creating a method on my collection which gets passed the server_response.

It would get all of the id attributes of the server_response, and all of the id attributes of the models already in the collection.

The difference of the id's in response - collection would = added models, and vice versa would be removed models. Add and remove those models respectively from the collection.

The intersection of both sets of id's would be the modified models, so iterate through these id's and simply do a collection.get(id).set(attributes).

In pseudocoffeescript:

merge: (server_response) =>
  response_ids = _.pluck(server_response, 'id')
  collection_ids = @pluck('id')

  added = _.difference(response_ids, collection_ids)

  for add in added
    @add(_.find(server_response, (model) ->
      return model.id == add
    ))

  removed = _.difference(collection_ids, response_ids)

  for remove in removed
    @remove(@get(remove))

  changed = _.intersection(response_ids, collection_ids)

  for change in changed
    @get(change).set(_.find(server_response, (model) ->
      return model.id == change
    ))
like image 455
Jorge Israel Peña Avatar asked Nov 21 '11 12:11

Jorge Israel Peña


1 Answers

This technique is useful sometimes. We extend Collection with the following method. This should do what you're looking for. It's not in coffee, but you could easily port it. Enjoy!

// Take an array of raw objects
// If the ID matches a model in the collection, set that model
// If the ID is not found in the collection, add it
// If a model in the collection is no longer available, remove it
freshen: function (objects) {
    var model;
    // Mark all for removal

    this.each(function (m) {
        m._remove = true;
    });

    // Apply each object
    _(objects).each(function (attrs) {
        model = this.get(attrs.id);
        if (model) {
            model.set(attrs); // existing model
            delete model._remove
        } else {
            this.add(attrs); // new model
        }
    }, this);

    // Now check for any that are still marked for removal
    var toRemove = this.filter(function (m) {
        return m._remove;
    })

    _(toRemove).each(function (m) {
        this.remove(m);
    }, this);
    this.trigger('freshen', this);
}
like image 80
maxl0rd Avatar answered Sep 23 '22 01:09

maxl0rd