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.
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
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 objetoitems
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 eminitialize
em seu métodoconstructor
, e usaron
vez debind
(que é semi-obsoleto).Acho que é muito mais fácil ter atributos como um hash superficial (sem coleções aninhadas, modelos, etc.), onde possível.
Isso parece funcionar bem até mesmo para esquemas profundamente aninhados. (veja a documentação )