A Backbone.js Collection of multiple Model subclasses
Solution 1
There is indeed.
When you call 'fetch' on a collection, it passes the response through Backbone.Collection.parse before adding it to the collection.
The default implementation of 'parse' just passes the response through, as is, but you can override it to return a list of models to be added to the collection:
class Logbooks extends Backbone.Collection
model: Logbook
url: 'api/logbooks'
parse: (resp, xhr) ->
_(resp).map (attrs) ->
switch attrs.type
when 'UML' then new UmlLogbook attrs
when 'Plane' then new PLaneLogbook attrs
EDIT: whoa, idbentley got there before me. the only difference being he used 'each' and I used 'map'. Both will work, but differently.
Using 'each' effectively breaks the chain that the 'fetch' call started (by returning 'undefined' - the subsequent call to 'reset' (or 'add') therefore will do nothing) and does all the processing right there in the parse function.
Using 'map' just transforms the list of attributes into a list of models and passes it back to the chain already in motion.
Different strokes.
EDIT AGAIN: just realized there's also another way to do this:
The 'model' attribute on a collection is there only so the collection knows how to make a new model if it's passed attributes in 'add', 'create' or 'reset'. So you could do something like:
class Logbooks extends Backbone.Collection
model: (attrs, options) ->
switch attrs.type
when 'UML' then new UmlLogbook attrs, options
when 'Plane' then new PLaneLogbook attrs, options
# should probably add an 'else' here so there's a default if,
# say, no attrs are provided to a Logbooks.create call
url: 'api/logbooks'
The advantage of this is that the collection will now know how to 'cast' the right subclass of Logbook for operations other than 'fetch'.
Solution 2
Yes. You can override the parse
function on the collection (I'm gonna use javascript instead of coffeescript, because it's what I know, but the mapping should be easy):
LogbookCollection = Backbone.Collection.extend({
model: Logbook,
url: "/api/logbooks",
parse: function(response){
var self = this;
_.each(response, function(logbook){
switch(logbook.type){
case "ULM":
self.add(new UmlLogBook(logbook);
break;
case "Plane":
...
}
}
}
});
Hope this helps.
Solution 3
as of backbone 0.9.1, i've started using the method described in esa-matti suuronen's pull-request:
https://github.com/documentcloud/backbone/pull/1148
after applying the patch, your collection would be something like this:
LogbookCollection = Backbone.Collection.extend({
model: Logbook,
createModel: function (attrs, options) {
if (attrs.type === "UML") { // i'am assuming ULM was a typo
return new UmlLogbook(attrs, options);
} else if (attrs.type === "Plane") {
return new Plane(attrs, options);
} else {
return new Logbook(attrs, options);
// or throw an error on an unrecognized type
// throw new Error("Bad type: " + attrs.type);
}
}
});
i believe this would fit since you're using STI (all models have unique ids)
Solution 4
parse
can work on its own, or you could use the submodelTypes feature of Backbone-Relational.
Tricote
Updated on June 06, 2022Comments
-
Tricote almost 2 years
I have a REST Json API that returns a list "logbooks". There are many types of logbooks that implement different but similar behavior. The server side implementation of this on the Database layer is a sort of Single Table Inheritance, so each JSON representation of a logbook contains its "type" :
[ {"type": "ULM", "name": "My uml logbook", ... , specific_uml_logbook_attr: ...}, {"type": "Plane", "name": "My plane logbook", ... , specific_plane_logbook_attr: ...} ]
I would like to replicate this server model on the client side, so I have a base
Logbook
class and multiple logbook sub classes :class Logbook extends Backbone.Model class UmlLogbook extends Logbook class PlaneLogbook extends Logbook ...
My
Backbone.Collection
is a set ofLogbook
models that i use to query the JSON API :class LogbookCollection extends Backbone.Collection model: Logbook url: "/api/logbooks"
When I fetch the logbook collection, is there a way to cast each
Logbook
to its corresponding sub class (based on the JSON "type" attribute) ? -
Tricote almost 13 yearsThanks for your very complete answer! I knew the existence of the parse method, but I didn't know the result was directly passed to the reset method... Should have dig the source code better ! Thx again
-
Dmitry about 12 yearsYou don't really need to use eval here, new App.Models[attrs.type](attrs) will work just fine.
-
philoye almost 12 yearsYour edit using the
model
method is much better than usingparse
because as you rightly point out it works forreset
. This way whether the new models come in viafetch
or bootstrapped in the HTML it still works. Thanks! -
corroded almost 12 yearsi suggest you move up that last part so people see it more :) just a thought
-
jordancooperman over 11 yearsYes, definitely prefer the second method. Simple and elegant, thanks so much!
-
Emile Bergeron almost 8 yearsPlease note that the model function breaks
this.model.prototype.idAttribute
and may cause duplicates. Take a look at this answer which solves it withModelFactory.prototype.idAttribute
. -
Emile Bergeron almost 8 yearsTo add to my previous comment, you could also override collection's
modelId
function.