gmac / backbone.epoxy

Declarative data binding and computed models for Backbone
http://epoxyjs.org
MIT License
615 stars 89 forks source link

When using as a mixin, repeated _super calls #114

Closed kevincolten closed 8 years ago

kevincolten commented 9 years ago

Using a mixin library such as Backbone.Cocktail to maintain functionality for two disparate libraries, for instance Backbone.Epoxy.Model and Backbone.RelationalModel, you get the benefit of utilizing the important functions in both, like get() and set(), without overriding. Here is an example of mixing them in, copy/pasted from https://github.com/gmac/backbone.epoxy/issues/108:

Backbone.Epoxy.Model.prototype._super = Backbone.RelationalModel;
var baseModel = Backbone.RelationalModel.extend({});
var initMixin = {
    initialize: function() {
        this.initComputeds();
    }
};
Cocktail.mixin(baseModel, Backbone.Epoxy.Model.mixin(), initMixin);
var relationalEpoxyModel = new baseModel();

and your destroy method, afterwards, would function like this:

destroy: function(options) {

    //Backbone.Model's destroy via Backbone.RelationalModel's lack of overwriting
    options = options ? _.clone(options) : {};
        var model = this;
        var success = options.success;

        var destroy = function() {
            model.trigger('destroy', model, model.collection, options);
        };

        options.success = function(resp) {
            if (options.wait || model.isNew()) destroy();
            if (success) success(model, resp, options);
            if (!model.isNew()) model.trigger('sync', model, resp, options);
        };

        if (this.isNew()) {
            options.success();
            return false;
        }
        wrapError(this, options);

        var xhr = this.sync('delete', this, options);
        if (!options.wait) destroy();
        return xhr;

    //from Epoxy.Model's destroy()
    this.clearComputeds();
    return _super(this, 'destroy', arguments);  //this will call Backbone.Model's destroy, as Backbone.Model is set as _super
}

note: Cocktail does something a little more complicated, this just illustrates the functionality

The issue here is that the model's destroy() call will be called twice. Is there a better method anyone can think of to better utilize the mixin functionality of Epoxy.Model?

My current solution was to just comment out a few extra calls to _super:

...
constructor: function(attributes, options) {
     _.extend(this, _.pick(options||{}, modelProps));
     // _super(this, 'constructor', arguments);
     this.initComputeds(attributes, options);
},
...
get: function(attribute) {
      // Automatically register bindings while building out computed dependency graphs:
      modelMap && modelMap.push(['change:'+attribute, this]);

      // Return a computed property value, if available:
      if (this.hasComputed(attribute)) {
        return this.c()[ attribute ].get();
      }

      // Default to native Backbone.Model get operation:
      // return _super(this, 'get', arguments);
},
...
destroy: function() {
    this.clearComputeds();
    // return _super(this, 'destroy', arguments);
},

though I haven't extensively tested the downstream effects.

gmac commented 8 years ago

This is a very specific use case and goes far beyond the scope of the library itself. Making disparate bits of software place nice is difficult, and I wish you luck! My best advice would be to think about application design where the bits are NOT mixed. Epoxy is best used for display concerns. If you have heavy data concerns, perhaps do that as a separate layer of your application, and then build a display layer infront of it.