Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A Backbone.js Collection of multiple Model subclasses

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 of Logbook 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) ?

like image 329
Tricote Avatar asked Aug 03 '11 21:08

Tricote


2 Answers

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'.

like image 173
satchmorun Avatar answered Oct 02 '22 18:10

satchmorun


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.

like image 37
idbentley Avatar answered Oct 02 '22 18:10

idbentley