【问题标题】:A Backbone.js Collection of multiple Model subclasses多个模型子类的 Backbone.js 集合
【发布时间】:2011-10-19 11:56:20
【问题描述】:

我有一个返回列表“日志”的 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

...

我的 Backbone.Collection 是一组 Logbook 模型,用于查询 JSON API:

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

当我获取日志集合时,有没有办法将每个 Logbook 转换为其对应的子类(基于 JSON“类型”属性)?

【问题讨论】:

    标签: json inheritance backbone.js coffeescript


    【解决方案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
    

    编辑:哇,idbentley 在我之前到达那里。唯一的区别是他使用了“每个”,而我使用了“地图”。两者都可以,但不同。

    使用 'each' 有效地打破了 'fetch' 调用开始的链(通过返回 'undefined' - 随后对 'reset'(或 'add')的调用因此不会执行任何操作)并在那里完成所有处理在解析函数中。

    使用 'map' 只是将属性列表转换为模型列表并将其传递回已经在运行的链。

    不同的笔画。

    再次编辑:刚刚意识到还有另一种方法可以做到这一点:

    集合上的“模型”属性只是为了让集合知道如何在“添加”、“创建”或“重置”中传递属性时创建新模型。所以你可以这样做:

    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'
    

    这样做的好处是集合现在知道如何“转换” Logbook 的正确子类以用于“获取”以外的操作。

    【讨论】:

    • 感谢您非常完整的回答!知道parse方法的存在,却不知道结果是直接传给reset方法的……应该挖源码比较好!再次感谢
    • 您使用model 方法进行的编辑比使用parse 好得多,因为正如您正确指出的那样,它适用于reset。这样,无论新模型是通过 fetch 进入还是在 HTML 中引导,它仍然可以工作。谢谢!
    • 我建议你把最后一部分往上移,这样人们会看到更多:) 只是一个想法
    • 是的,绝对更喜欢第二种方法。简洁优雅,非常感谢!
    • 请注意,模型函数会破坏this.model.prototype.idAttribute,可能会导致重复。看看这个answerModelFactory.prototype.idAttribute 解决它。
    【解决方案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(所有模型都有唯一的 id)

      【讨论】:

        【解决方案4】:

        parse 可以单独工作,也可以使用Backbone-RelationalsubmodelTypes 功能。

        【讨论】:

          【解决方案5】:

          也许使用 eval 不好,但这更像是 ruby​​ 风格的方式(coffeescript):

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

          所以您不需要编写很多开关/案例,只需在 JSON 中设置类型属性即可。它适用于 rails+citier 或其他多表继承解决方案。您可以添加新的后代而不将它们添加到您的案例中。

          您可以在其他需要大量开关/案例的地方使用这种结构,具体取决于您的模型类。

          【讨论】:

          • 这里不需要使用 eval,new App.Models[attrs.type](attrs) 就可以了。
          猜你喜欢
          • 2011-10-31
          • 1970-01-01
          • 2012-07-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-01
          • 2013-05-29
          • 2013-07-24
          相关资源
          最近更新 更多