Backbone: Qual é a melhor maneira de modelar uma coleção dentro de um modelo?

Criado em 4 nov. 2010  ·  11Comentários  ·  Fonte: jashkenas/backbone

Minha equipe está trabalhando com um esquema de dados interessante. Basicamente, o que temos é um documento que possui um nome e uma versão e uma série de itens que pertencem a ele. Os próprios itens não têm ids associados a eles porque pertencem ao modelo maior, mas os itens podem ser adicionados / editados / excluídos do documento principal. O modelo é mais ou menos assim:
{
nome: "Teste",
versão 1,
Itens : [
{nome: "Item 1",
posição: 0},
{nome: "Item 2",
posição: 1}]
}

Seria ótimo usar uma coleção para os itens subjacentes no modelo, mas sempre que a coleção for atualizada, o modelo deve postar de volta ao servidor com seu url. Qual é a melhor forma de modelar isso no Backbone? Ficarei feliz em postar mais em uma essência se mais informações forem necessárias.

question

Comentários muito úteis

@eranation : não, deve ser praticamente o mesmo. Ao usar esse padrão, gosto de manter items fora do hash de meus atributos, para não ter que mantê-los sincronizados. Isso requer que você retire o objeto items da resposta para análise e o adicione de volta para serialização (veja abaixo). Além disso, você pode querer colocar a lógica que @rsim colocou em initialize em seu método constructor , e usar on vez de bind (que é semi-obsoleto).

Acho que é muito mais fácil ter atributos como um hash superficial (sem coleções aninhadas, modelos, etc.), onde possível.

var Document = Backbone.Model.extend({
  constructor: function() {
    this.items = new ItemSet(null, {document: this});
    this.items.on('change', this.save, this);
    Backbone.Model.apply(this, arguments);
  },
  parse: function(resp) {
    this.items.set(resp.items, {parse: true, remove: false});
    delete resp.items;
    return resp;
  },
  toJSON: function() {
    var attrs = _.clone(this.attributes);
    attrs.items = this.items.toJSON();
    return attrs;
  }
});
var ItemSet = Backbone.Collection.extend({
  model: Item,
  initialize: function(models, options) {
    this.document = options.document;
  }
});
var Item = Backbone.Model.extend({
  // access document with this.collection.document
});
var document1 = new Document({
  name: "Test",
  version: 1,
  items: [
    {name : "Item 1", position : 0},
    {name : "Item 2", position : 1}
  ]
});

Isso parece funcionar bem até mesmo para esquemas profundamente aninhados. (veja a documentação )

Todos 11 comentários

Eu diria que você tem duas opções principais ... A primeira é deixar os itens como um atributo básico. O backbone usa uma verificação de igualdade profunda quando os atributos mudam, portanto, se um item interno for atualizado, o documento saberá sobre ele.

A outra opção é retirar os itens do documento e anexá-los como modelos por conta própria, dentro de uma coleção presa no documento (fazemos algo nesse sentido em DocumentCloud). Por exemplo (falando grosso modo):

var Document = Backbone.Model.extend({
  initialize: function() {
    this.items = new ItemSet();
    this.items.bind('change', this.save);
  }
});

Algum deles funciona bem para você?

Eu recomendaria também adicionar a propriedade "document" de volta ao objeto Document, que pode ser acessado quando necessário a partir dos itens:

var Document = Backbone.Model.extend({
  initialize: function() {
    this.items = new ItemSet(this.get('items'), {document: this});
    this.items.bind('change', this.save);
  }
});
var ItemSet = Backbone.Collection.extend({
  initialize: function(models, options) {
    this.document = options.document;
  }
});
var Item = Backbone.Model.extend({
  // access document with this.collection.document
});
var document1 = new Document({
  name: "Test",
  version: 1,
  items: [
    {name : "Item 1", position : 0},
    {name : "Item 2", position : 1}
  ]
});

Acredito que vincular o evento de alteração dos itens ao salvamento do documento é a peça que faltava. Embora, adicionar o documento ao ItemSet também pareça ser muito útil. Vamos tentar isso hoje e vou deixar vocês saberem como ficou.

Isso funcionou bem. O maior problema que estamos tendo agora é com o mongóide. Obrigado pela ajuda pessoal.

Como eu salvaria todos os itens de uma vez? Não quero fazer uma solicitação cada vez que um item é alterado, mas quero salvá-los todos com uma única solicitação na ação do usuário. Eu estava pensando em coletar tudo quando o documento for salvo e atualizar os atributos de 'itens' do documento. Essa é uma boa solução?

Contanto que seus itens estejam configurados no documento, quando você fizer um document.save (), os itens serão enviados para o servidor também.

Mas digamos que se eu adicionar um item à coleção document1.items em tempo de execução, ele não será adicionado ao atributo 'items' do document1 também de forma automática. Portanto, se eu fizer um document1.save (), o novo modelo que adicionei à coleção não será enviado ao servidor. Não consigo ver como as alterações na coleção podem se propagar nos atributos do modelo que são enviados com o salvamento.

Então, aqui está como estamos lidando com isso: o documento tem uma matriz de itens padrão. Na inicialização, em um método de conjunto sobrecarregado, crio uma nova coleção de itens a partir dos atributos e a defino no documento.

class Document extends Backbone.Model
  defaults:
    items: []

  set: (attrs, options) ->
    items = attrs['items']
    if _( items ).isArray()
      if _( items ).isEmpty()
        attrs['items'] = new DocumentItemsCollection
        newItem = new Item
        attrs['items'].add(newItem, { silent: true })
      else
        attrs['items'] = new DocumentItemsCollection items

Nesse ponto, você apenas lida com os métodos de coleta de itens com 'get', 'set', 'add' e 'remove'. Você não mexe com a notação de ponto. Tenho até métodos em minha classe Document chamados addItem e deleteItem para disparar eventos de alteração no próprio documento. Quando você salva () no documento, ele chama toJSON em sua coleção de itens.

Honestamente, este é apenas um caso simples para nossos documentos e temos subdocumentos ainda mais profundos. Lidar com essa quantidade de complexidade com backbone, e sobrecarregar vários dos métodos nos modelos, é realmente um grande pé no saco. Estamos agora procurando substituir o backbone pelo sproutcore no futuro.

Se você estiver lidando com documentos realmente complexos, sugiro olhar para ExtJS ou sproutcore. O backbone é ótimo para um projeto pequeno com modelos simples, mas se desintegra muito rapidamente quando os objetos / interações começam a aumentar.

Existem novas "práticas recomendadas" para isso para 1.0?

@eranation : não, deve ser praticamente o mesmo. Ao usar esse padrão, gosto de manter items fora do hash de meus atributos, para não ter que mantê-los sincronizados. Isso requer que você retire o objeto items da resposta para análise e o adicione de volta para serialização (veja abaixo). Além disso, você pode querer colocar a lógica que @rsim colocou em initialize em seu método constructor , e usar on vez de bind (que é semi-obsoleto).

Acho que é muito mais fácil ter atributos como um hash superficial (sem coleções aninhadas, modelos, etc.), onde possível.

var Document = Backbone.Model.extend({
  constructor: function() {
    this.items = new ItemSet(null, {document: this});
    this.items.on('change', this.save, this);
    Backbone.Model.apply(this, arguments);
  },
  parse: function(resp) {
    this.items.set(resp.items, {parse: true, remove: false});
    delete resp.items;
    return resp;
  },
  toJSON: function() {
    var attrs = _.clone(this.attributes);
    attrs.items = this.items.toJSON();
    return attrs;
  }
});
var ItemSet = Backbone.Collection.extend({
  model: Item,
  initialize: function(models, options) {
    this.document = options.document;
  }
});
var Item = Backbone.Model.extend({
  // access document with this.collection.document
});
var document1 = new Document({
  name: "Test",
  version: 1,
  items: [
    {name : "Item 1", position : 0},
    {name : "Item 2", position : 1}
  ]
});

Isso parece funcionar bem até mesmo para esquemas profundamente aninhados. (veja a documentação )

@ akre54 obrigado, este é um ótimo exemplo, muito apreciado

Esta página foi útil?
0 / 5 - 0 avaliações