web-dev-qa-db-fra.com

Une collection Backbone.js de plusieurs sous-classes de modèle

J'ai un REST API Json qui renvoie une liste de "journaux de bord". Il existe de nombreux types de journaux de bord qui implémentent un comportement différent mais similaire. L'implémentation côté serveur de cela sur la couche base de données est une sorte de Héritage de table unique, donc chaque représentation JSON d'un journal de bord contient son "type":

[
  {"type": "ULM", "name": "My uml logbook", ... , specific_uml_logbook_attr: ...},
  {"type": "Plane", "name": "My plane logbook", ... , specific_plane_logbook_attr: ...}
]

Je voudrais répliquer ce modèle de serveur côté client, j'ai donc une classe de base Logbook et plusieurs sous-classes de journal de bord:

class Logbook extends Backbone.Model

class UmlLogbook extends Logbook

class PlaneLogbook extends Logbook

...

Ma Backbone.Collection est un ensemble de Logbook modèles que j'utilise pour interroger l'API JSON:

class LogbookCollection extends Backbone.Collection
  model: Logbook
  url: "/api/logbooks"

Lorsque je récupère la collection de journaux de bord, existe-t-il un moyen de convertir chaque Logbook en sa sous-classe correspondante (basée sur l'attribut "type" JSON)?

63
Tricote

Il y en a en effet.

Lorsque vous appelez "récupérer" sur une collection, il transmet la réponse via Backbone.Collection.parse avant de l'ajouter à la collection.

L'implémentation par défaut de 'parse' transmet simplement la réponse telle quelle, mais vous pouvez la remplacer pour renvoyer une liste de modèles à ajouter à la 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 est arrivé avant moi. la seule différence étant qu'il a utilisé "chacun" et j'ai utilisé "carte". Les deux fonctionneront, mais différemment.

L'utilisation de 'each' rompt effectivement la chaîne que l'appel 'fetch' a commencée (en renvoyant 'undefined' - l'appel suivant à 'reset' (ou 'add') ne fera donc rien) et fait tout le traitement directement dans l'analyse fonction.

L'utilisation de "map" transforme simplement la liste d'attributs en une liste de modèles et la retransmet à la chaîne déjà en mouvement.

Différents coups.

MODIFIER ENCORE: vient de réaliser qu'il existe également une autre façon de procéder:

L'attribut "modèle" sur une collection n'est là que pour que la collection sache comment créer un nouveau modèle si ses attributs sont passés dans "ajouter", "créer" ou "réinitialiser". Vous pourriez donc faire quelque chose comme:

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'

L'avantage de ceci est que la collection saura désormais "transtyper" la bonne sous-classe de Logbook pour des opérations autres que "extraire".

81
satchmorun

Oui. Vous pouvez remplacer la fonction parse sur la collection (je vais utiliser javascript au lieu de coffeescript, car c'est ce que je sais, mais le mappage devrait être facile):

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":
               ...
          }
      }
    }
 });

J'espère que cela t'aides.

11
idbentley

depuis le backbone 0.9.1, j'ai commencé à utiliser la méthode décrite dans la pull-request d'esa-matti suuronen:

https://github.com/documentcloud/backbone/pull/1148

après avoir appliqué le patch, votre collection ressemblerait à ceci:

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);
        }
    }

});

je crois que cela conviendrait puisque vous utilisez STI (tous les modèles ont des identifiants uniques)

3
pinge

parse peut fonctionner seul, ou vous pouvez utiliser la fonction submodelTypes de Backbone -Relationnel .

1
philfreo

Peut-être que c'est mauvais d'utiliser eval, mais c'est beaucoup plus de façon Ruby (coffeescript):

  parse: (resp)->
    _(resp).map (attrs) ->
      eval("new App.Models.#{attrs.type}(attrs)")

Vous n'avez donc pas besoin d'écrire beaucoup de commutateurs/cas, il suffit de définir l'attribut type dans votre JSON. Cela fonctionne très bien avec Rails + citier ou une autre solution d'héritage multitable. Vous pouvez ajouter de nouveaux descendants sans les ajouter à vos cas.

Et vous pouvez utiliser de telles constructions dans d'autres endroits où vous avez besoin de beaucoup de commutateurs/boîtiers en fonction de votre classe de modèle.

0
Alexander Ulitin