2

My API response:

{
  firstName: '',
  lastName: '',
  notifications: [
    {
      category: '1',
      subcategory: '',
      source: '',
      optInDate: '',
      optOutDate: '',
      active: ''
    },
    {
      category: '2',
      subcategory: '',
      source: '',
      optInDate: '',
      optOutDate: '',
      active: ''
    }
  ]
}

My corresponding Backbone implementation for the response is:

var Notification = Backbone.Model.extend({
  initialize: function (args) {  },
});

var Notifications = Backbone.Collection.extend({
  model: Notification,
  url: '/xyz/abc',

  parse: function (data) {
    return data.notifications;
  },
});

I want to only update the "category 1" model (this is always true) with say active: true but persist the whole collection in my request payload and not just the changed model. How do we accomplish this with backbone?

I have tried getting that particular model and doing a model.set({active: true}) and calling model.save() but that sends only that model to the server.

I need the whole response to be persisted back to the server along with the updated model.

EDIT:

I was able to achieve this with @Khang's help in his answer below. However I forgot to mention that I need to send other attributes too along with the collection i.e, firstName, lastName from the above response.

I'm overriding the parse method to include them as below:

parse: function(data) { 
  this.firstName = data.firstName; 
  this.lastName = data.lastName; 
  return data.notifications; 
} 

When I call Backbone.sync, it still sends just the collection in the payload. Is that because I am returning just the notifications from data object in the parse method?

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
arjary
  • 169
  • 1
  • 12

2 Answers2

3

I'd say don't use sync like Khang suggests and overriding the Backbone.sync function should be done with extreme caution since it is shared by every models and collections.

While it works for your use-case right now, it's more of a patch in the long run.


the API expects the whole DTO to be sent back in the payload to maintain RESTFul

If the API was truly RESTful, it would provide an endpoint to manage the notifications individually.

As a reference to future readers, make an endpoint to manage models individually. This is the way to go when creating a REST API. I go over the pros and cons of this technique with Backbone in mind in another answer.

API is not open to changing their implementation

Since you can't, that means the API response is representing a model, not a collection. Backbone offers predefined behaviours based on a RESTful API where an object should be a model, and an array of objects should be a collection (exception being when receiving metadata with a collection like page count, etc.) A Backbone Collection doesn't have a save function for this reason, it shouldn't be saved all at once.

In your case, it looks like a user model would probably do the job.

Note that there's a lot of alternatives to implement a collection within a model. Here's one quick example.

var UserModel = Backbone.Model.extend({
    urlRoot: '/xyz/abc',
    initialize: function(attrs, options) {
        // init a collection within the model
        this.notifications = new Notifications((attrs || {}).notifications);
        // update the collection whenever the model is synced.
        this.listenTo(this, 'sync', this.onSync());
    },

    // Useful abstraction that doesn't interfere with the normal use-case
    saveNotifications: function() {
        return this.save({
            notifications: this.notifications.toJSON()
        }, { patch: true });
    },

    onSync: function() {
        this.notifications.reset(this.get('notifications'));
    }
});

You can use the model like any other

var user = new UserModel({ id: 'user-id-1' });
user.fetch();

And when you need the notifications, they're already available within a handy collection.

var notif = user.notifications.findWhere({ category: '1' });
if (notif) {
    notif.set({ active: true });
    user.saveNotifications();
}

Backbone events like sync on the user model and reset on the notifications collection will trigger correctly.

When dealing with lots of data, there are downsides to this technique, which saving the collection all at once should bring to the light anyway. Lazy loading and paginating are ways to improve performance in this situation, and obviously, following REST best practices will help a lot.

At our company, our APIs were heavily inspired by "Build APIs you won't hate" and I'm glad that we're using this book.

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
0

I am not sure what you are trying to do, it does not seem right to me. If only one model change, why do you need to send the whole collection to server? Aren't the unchanged models already on server? I think you should reconsider this approach.

Anyway if you really need it, this would achieve what you want:

Backbone.sync("update", Notifications);

More docs here

Update:

If you have many extra data to send along every times the collection is sync, you can override sync method to have custom sync behavior for the collection. Otherwise you can use options.attrs to have custom data when you need it:

Backbone.sync("update", Notifications, {
    attrs: {
        data: Notifications.toJSON(),
        firstName: 'foo',
        lastName: 'bar',
    }
});

To understand why this works, I suggest you reading the Backbone source code Backbone.sync

kkkkkkk
  • 7,628
  • 2
  • 18
  • 31
  • the API expects the whole DTO to be sent back in the payload to maintain RESTFul and hence the whole collection needs to be sent back in the payload. API is not open to changing their implementation, so it needs to be done in the front end. How would Backbone.sync help? should the sync be called after model.set? and model.save() ? – arjary Dec 16 '17 at 18:02
  • You don't need to call `save`, just update models by `set` and call `sync` when you want to persist to db – kkkkkkk Dec 17 '17 at 06:30
  • yes that worked. Sorry i forgot to mention that i need to send the other attributes too along with the collection i.e, firstName, lastName from the above response. I overrided the parse method to include them as below - parse: function(data) { this.firstName = data.firstName; this.lastName = data.lastName; return data.notifications; } when i call Backbone.sync it still sends just the collection in the payload. Is that because i am returning just the notifications from data object in the parse method? How could i achieve this? Thanks for you help ! – arjary Dec 18 '17 at 02:32
  • `parse` is used to take care of data sent from server, you can not use it here. Check my updated answer – kkkkkkk Dec 18 '17 at 03:35