Коллекция Backbone.js нескольких подклассов Model - PullRequest
63 голосов
/ 04 августа 2011

У меня есть REST Json API, который возвращает список «бортовых журналов».Есть много типов журналов, которые реализуют различное, но похожее поведение.Реализация этого на уровне сервера на уровне базы данных является своего рода наследованием отдельных таблиц, поэтому каждое JSON-представление журнала содержит свой «тип»:

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

Я хотел бы скопировать эту модель сервера нана стороне клиента, поэтому у меня есть базовый класс Logbook и несколько подклассов журнала:

class Logbook extends Backbone.Model

class UmlLogbook extends Logbook

class PlaneLogbook extends Logbook

...

My Backbone.Collection - это набор Logbook моделей, которые я использую для запроса JSON API:

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

Когда я получаю коллекцию журнала, есть ли способ привести каждый Logbook к соответствующему подклассу (основанному на атрибуте типа JSON)?

Ответы [ 5 ]

81 голосов
/ 04 августа 2011

Да, действительно.

Когда вы вызываете 'fetch' для коллекции, она передает ответ через Backbone.Collection.parse перед добавлением его в коллекцию.

Реализация по умолчанию 'parse' просто пропускает ответ, как есть, но вы можете переопределить его, чтобы вернуть список моделей, которые будут добавлены в коллекцию:

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

РЕДАКТИРОВАТЬ: Вау, idbentley добрался до меня. единственная разница в том, что он использовал «каждый», а я использовал «карту». Оба будут работать, но по-разному.

Использование 'each' эффективно разрывает цепочку, которую начал вызов 'fetch' (возвращая 'undefined' - следовательно, последующий вызов 'reset' (или 'add') ничего не сделает) и выполняет всю обработку прямо там в функции разбора.

Использование 'map' просто преобразует список атрибутов в список моделей и передает его обратно в цепочку, которая уже находится в движении.

Различные штрихи.

ВНОВЬ РЕДАКТИРОВАТЬ: только что понял, что есть еще один способ сделать это:

Атрибут 'model' в коллекции существует только для того, чтобы коллекция знала, как создать новую модель, если ей переданы атрибуты в 'add', 'create' или 'reset'. Таким образом, вы можете сделать что-то вроде:

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'

Преимущество этого заключается в том, что теперь коллекция будет знать, как «разыграть» правильный подкласс журнала для операций, отличных от «извлечения».

11 голосов
/ 04 августа 2011

Да.Вы можете переопределить функцию parse в коллекции (я буду использовать javascript вместо coffeescript, потому что это то, что я знаю, но сопоставление должно быть простым):

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

Надеюсь, это поможет.

3 голосов
/ 31 марта 2012

на уровне магистрали 0.9.1, я начал использовать метод, описанный в запросе тяги esa-matti suuronen:

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

после применения патча ваша коллекциябыть примерно таким:

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

});

я считаю, что это подойдет, так как вы используете STI (все модели имеют уникальные идентификаторы)

1 голос
/ 04 февраля 2013

parse может работать самостоятельно, или вы можете использовать функцию submodelTypes Backbone-Relational .

0 голосов
/ 01 февраля 2012

Может быть, плохо использовать eval, но это гораздо больше в стиле ruby ​​(coffeescript):

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

Так что вам не нужно писать много переключателей / кейсов, просто установите типатрибут в вашем JSON.Он очень хорошо работает с rails + citier или другим решением для наследования.Вы можете добавлять новых потомков, не добавляя их в свои дела.

И вы можете использовать такие конструкции в других местах, где вам нужно много переключателей / дел в зависимости от класса вашей модели.

...