jlstr
jlstr

Reputation: 3056

Override Backbone.sync() at Model level to send extra params?

To be quite honest I'm stuck trying to override Backbone's sync() method for a Model, I have the signature for the function in place, and it gets triggered correctly, but I don't know what to put in the function body in order for it to make a default call to DELETE but with extra arguments. ie.

class Master.Models.Member extends Backbone.Model
  urlRoot: '/api/members/'

  sync: (method, model, options) ->
    params = _.clone options
    Backbone.sync method, model, params

I call it like this:

......
remove: ->
  @model.destroy
    collective_id: the_id

My intention there, is to pass the collective_id param you see there to the server. But even though it's inside the options hash for sync() and I clone it, It won't make it to the server! How can I send that extra param to the server?

(As it is, the only thing that reaches the server is the Model's id)

Thanks in advance!

Upvotes: 25

Views: 20808

Answers (2)

Cory Danielson
Cory Danielson

Reputation: 14501

When you call .destroy(), .fetch() or .save() they all call Model.sync which only calls Backbone.sync. It's a proxy function. This is intended to provide a nice hook for modifying the AJAX behavior of a single model or any models that extend from that model.

  • Solution 1: Override the Global Backbone.sync to JSON.stringify and modify the contentType when you've specified data to be sent with the delete request.
    • Pros: You can call model.destroy() and optionally pass in an options parameter
  • Solution 2 - Override the Model.sync method.
    • Pros: The override only affects individual models. Isolated changes.
    • Cons: All models that wish to delete with data need to extend from the correct 'base model'
  • Solution 3 - Don't override anything and explicitly call model.sync with all of the stringify and contentType stuff.
    • Pros: Very isolated changes, won't affect any other models. Useful if you're integrating with a large codebase.

[Solution 1] - Global Override of Backbone.sync (this will affect all models)

javacript version

var oldBackboneSync = Backbone.sync;
Backbone.sync = function( method, model, options ) {
    // delete request WITH data
    if ( method === 'delete' && options.data ) {
        options.data = JSON.stringify(options.data);
        options.contentType = 'application/json';
    } // else, business as usual.
    return oldBackboneSync.apply(this, [method, model, options]);
}

Usage:

var model, SomeModel = Backbone.Model.extend({ /* urlRoot, initialize, etc... */});
model = new SomeModel();
model.destroy({
    data: {
        /* data payload to send with delete request */
    }
});

[Solution 2] - Override Backbone.destroy on a base model and extend other models from that.

override

// Create your own 'enhanced' model 
Backbone.EnhancedModel = Backbone.Model.extend({
    destroy: function( options ) {
        if ( options.data ) {
            // properly formats data for back-end to parse
            options.data = JSON.stringify(options.data);
        }
        // transform all delete requests to application/json
        options.contentType = 'application/json';
        Backbone.Model.prototype.destroy.call(this, options);
    }
});

usage

var model, SomeModel = Backbone.EnhancedModel.extend({ /* urlRoot, initialize, etc... */})
model = new SomeModel();
SomeModel.destroy({
    data: {
        /* additional data payload */
    }
}); 

[Solution 3] - Call .destroy() with correct parameters.

If sending data with your destroy requests is an isolated thing, then this is an adequate solution.

When calling model.destroy() pass in the data and contentType options like so:

javascript version/usage

var additionalData = { collective_id: 14 };
model.destroy({
    data: JSON.stringify(additionalData),
    contentType: 'application/json'
});

The "Problem" (with Backbone, not solutions):

Backbone.js makes the assumption (view source) that delete requests do not have a data payload.

// delete methods are excluded from having their data processed and contentType altered.
if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
      params.contentType = 'application/json';
      params.data = JSON.stringify(options.attrs || model.toJSON(options));
}

In their assumed RESTful API call, the only data required is the ID which should be appended to a urlRoot property.

var BookModel = Backbone.Model.extend({
    urlRoot: 'api/book'
});
var book1 = new BookModel({ id: 1 });
book1.destroy()

The delete request would be sent like

DELETE => api/book/1
contentType: Content-Type:application/x-www-form-urlencoded; charset=UTF-8

Upvotes: 45

glortho
glortho

Reputation: 13200

Params need to be sent in options.data, so try:

coffeescript

remove: () ->
  @model.destroy 
    data: JSON.stringify
      collective_id: the_id, 
    contentType: 'application/json'

javascript

remove: function() {
  return this.model.destroy({
    data: JSON.stringify({
      collective_id: the_id
    }),
    contentType: 'application/json'
  });
}

Upvotes: 5

Related Questions