A Backbone.js Коллекция нескольких подклассов Model

У меня есть 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, которые я использую для запроса API JSON:

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

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

Ответ 1

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

Когда вы вызываете '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

EDIT: whoa, idbentley добрался до меня. с той лишь разницей, что он использовал "каждый", и я использовал "карту". Оба будут работать, но по-другому.

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

Использование "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 a default if,
      # say, no attrs are provided to a Logbooks.create call

  url: 'api/logbooks'

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

Ответ 2

Да. Вы можете переопределить функцию 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

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

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 (все модели имеют уникальные идентификаторы)

Ответ 4

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

Ответ 5

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

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

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

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