I have a collection of Animals.
App.Collections.Animals extends Backbone.Collection
model: App.Animal
url: '/animals/' #returns json
And these animal classes:
App.Models.Animal extends Backbone.Model
App.Models.Monkey extends App.Models.Animal
defaults:{type:'Monkey'}
App.Models.Cat extends App.Models.Animal
defaults:{type:'Cat'}
App.Models.Dog extends App.Models.Animal
defaults:{type:'Dog'}
When collection is filled with JSON (records contain the type attribute) I want models to be instantiated as sub-classed models (Monkey,Cat,Dog) and not as Animal. How can you achieve this?
From Backbone documentation:
A collection can also contain polymorphic models by overriding this
property with a function that returns a model.
var Library = Backbone.Collection.extend({
model: function(attrs, options) {
if (condition) {
return new PublicDocument(attrs, options);
} else {
return new PrivateDocument(attrs, options);
}
}
});
The solution is straightforward (pardon the JS, I don't know CoffeeScript):
var SmartZoo = Backbone.Collection.extend({
model: function (attrs, options) {
// This code assumes that the object looks something like '{ type: "Cat", ... }'.
switch (attrs.type) {
case 'Cat':
return new Cat(attrs, options);
case 'Dog':
return new Dog(attrs, options);
default: // Unknown subclass
return new Animal(attrs, options);
}
}
});
You have to:
- Include an attribute in your model from which you can infer the type of Backbone model to create. In this example, my objects contain an attribute called "type" whose value is the full name of the Backbone type that represents it. Be sure to set it in the defaults or initialize of your Model so that you can also add real model instances to the collection.
- Define the models property of your collection as a function. The first parameter to this function will be the raw JS object (if that's what you passed in), or the attributes object of a Backbone model. Either way, you can access your type field as a property of this object.
- Execute your logic to infer the proper model from your type field.
- Return an instance of the correct model from the models function.
Here is a JSFiddle that shows this polymorphic collection in action: http://jsfiddle.net/FiddlerOnTheTarmac/uR2Sa/
Override backbone collection's _prepareModel. The collection new uses subclasses when defined otherwise uses the default model.
class App.Collections.Animals extends Backbone.Collection
model: App.Models.Animal
_prepareModel: (attrs, options) ->
if attrs instanceof Backbone.Model
attrs.collection = @
return attrs
options || (options = {})
options.collection = @
model_class = APP.Models[attrs.ntype] or this.model
model = new model_class(attrs, options)
if (!model._validate(attrs, options))
false
else
model