63 votes

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

J'ai une API REST Json qui renvoie une liste de "journaux de bord". Il existe de nombreux types de journaux de bord qui mettent en œuvre des comportements différents mais similaires. La mise en œuvre côté serveur de cette couche de base de données est une sorte d'héritage de table unique, de sorte que chaque représentation JSON d'un livre 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 reproduire ce modèle de serveur du côté client, donc j'ai une base Logbook et plusieurs sous-catégories de journaux de bord :

class Logbook extends Backbone.Model

class UmlLogbook extends Logbook

class PlaneLogbook extends Logbook

...

Mon Backbone.Collection est un ensemble de Logbook 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 d'extraire de chaque journal de bord les informations suivantes Logbook à sa sous-classe correspondante (sur la base de l'attribut "type" de JSON) ?

81voto

satchmorun Points 7504

Il y a en effet.

Lorsque vous appelez "fetch" sur une collection, la réponse passe par Backbone.Collection.parse avant d'être ajoutée à la collection.

L'implémentation par défaut de "parse" ne fait que transmettre la réponse telle quelle, mais vous pouvez la surcharger 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 y est arrivé avant moi. La seule différence est qu'il a utilisé 'each' et que j'ai utilisé 'map'. Les deux fonctionnent, mais différemment.

L'utilisation de "each" permet de rompre la chaîne que l'appel "fetch" a initiée (en retournant "undefined" - l'appel suivant à "reset" (ou "add") ne fera donc rien) et d'effectuer tout le traitement directement dans la fonction d'analyse.

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

C'est une autre paire de manches.

EDIT : je viens de réaliser qu'il y a aussi un autre moyen de faire ça :

L'attribut 'model' d'une collection est là uniquement pour que la collection sache comment créer un nouveau modèle si on lui passe des attributs dans 'add', 'create' ou 'reset'. 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 cette méthode est que la collection saura désormais comment "couler" la bonne sous-classe de Logbook pour les opérations autres que "récupérer".

11voto

idbentley Points 2333

Oui. Vous pouvez remplacer le parse sur la collection (je vais utiliser javascript au lieu de coffeescript, parce que c'est ce que je connais, mais le mapping 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 vous aidera.

3voto

pinge Points 11

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

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

après avoir appliqué le patch, votre collection serait quelque chose comme 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 pense que cela conviendrait puisque vous utilisez STI (tous les modèles ont un identifiant unique).

1voto

philfreo Points 12382

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

0voto

Ulitiy Points 713

C'est peut-être une mauvaise idée d'utiliser eval, mais c'est une méthode plus proche du style ruby (coffeescript) :

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

Vous n'avez donc pas besoin d'écrire beaucoup de switch/cases, il suffit de définir l'attribut type dans votre JSON. Cela fonctionne très bien avec rails+citier ou toute 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 d'un grand nombre de commutateurs/cases en fonction de votre classe de modèle.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X