Mongoose: Model.save () não salva arrays incorporados

Criado em 10 nov. 2012  ·  26Comentários  ·  Fonte: Automattic/mongoose

Aninharei matrizes de documentos e, quando os altero e faço um .save () no modelo, ele não salva as alterações no documento aninhado.

Aqui estão meus modelos e esquemas (alguns omitidos por questões de brevidade):

var Votes = new mongoose.Schema({
    date: Date, 
    user_name: String,
    is_up: Boolean
});

var EventSchema = new mongoose.Schema({
  to_date: Date,
  from_date: Date,
  location: String,
  name: String,
  suggested_by: String,
  description: String,
  users: [EventUser],
  comments: [Comments],
  suggestions: [Event],
  votes: [Votes]
});

var Event = mongoose.model('Event', EventSchema);

Modelo antes de event.save () chamado:

{ 
     __v: 1,
    _id: 509e87e583ccbfa00e000004,
    description: 'Pfft',
    from_date: Sun Nov 11 2012 08:00:00 GMT-0500 (EST),
    location: 'Home',
    name: 'Whatever',
    suggested_by: 'No one',
    to_date: Sun Nov 11 2012 00:00:00 GMT-0500 (EST),
    votes: [],
    suggestions: 
     [ { users: [],
         comments: [],
         suggestions: [],
         votes: [],
         _id: 509e880883ccbfa00e000005,
         suggested_by: 'Some one',
         to_date: Sun Nov 11 2012 04:00:00 GMT-0500 (EST),
         from_date: Mon Nov 12 2012 00:00:00 GMT-0500 (EST),
         location: 'Home',
         name: 'Football',
         description: 'FOOTBALL!!' } ],
    comments: [],
    users: [] 
}

O mesmo objeto com os votos aninhados logo antes de event.save() ser chamado.

{
   "__v":1,
   "_id":"509e87e583ccbfa00e000004",
   "description":"Pfft",
   "from_date":"2012-11-11T13:00:00.000Z",
   "location":"Home",
   "name":"Whatever",
   "suggested_by":"No one",
   "to_date":"2012-11-11T05:00:00.000Z",
   "votes":[ ],
   "suggestions":
      [ {
         "users":[],
         "comments":[ ],
         "suggestions":[ ],
         "votes":
            [{
               "is_up":true,
               "date":"2012-11-10T18:05:25.796Z",
               "user_name":"No one"
            }],
         "_id":"509e880883ccbfa00e000005",
         "suggested_by":"Some one",
         "to_date":"2012-11-11T09:00:00.000Z",
         "from_date":"2012-11-12T05:00:00.000Z",
         "location":"Home",
         "name":"Football",
         "description":"FOOTBALL!!"
      }],
   "comments":[],
   "users":[]
}

Quando event.save () é chamado, nenhum erro é lançado, mas o esquema de votos aninhados dentro do esquema de eventos aninhados não é realmente salvo. Se eu usar a mesma lógica geral no objeto de evento de nível superior para salvar um voto, funciona.

Quando examinei o código, brevemente, parece que .save () é suposto ser um atalho para salvar novos objetos, bem como atualizar os que já existem.

Meu palpite é que Model.prototype._delta não está indo fundo o suficiente para capturar todos os objetos aninhados, https://github.com/LearnBoost/mongoose/blob/master/lib/model.js#L529

Comentários muito úteis

Eu vejo qual é o problema - caso clássico da primeira pergunta do FAQ do mangusto.

Obrigado, foi esse o problema no meu caso! :)

model.myArray[index] = anyValue

torna-se

model.myArray.set(index, anyValue)

Todos 26 comentários

Inclua também o código que você está usando para reproduzir o problema (onde você está manipulando o documento antes de salvar etc.)

Claro, publiquei de forma resumida para evitar uma parede de código aqui, https://gist.github.com/4055392

Incluí salvar um voto no evento de nível superior (funcionando) e também salvá-lo em um evento aninhado (não funciona). Como você pode ver, eles adicionam ou atualizam um voto e o salvam de maneira semelhante.

Tente corrigir seu EventSchema ref usado em suggestions :

// bad
var EventSchema = new mongoose.Schema({
  suggestions: [EventSchema] <== at this time, EventSchema is undefined which is interpreted as Mixed

// instead...

var EventSchema = new mongoose.Schema;
EventSchema.add({
  suggestions: [EventSchema] <== EventSchema exists
})


fechando, sem resposta. reabra se necessário.

Eu tenho o mesmo problema onde .save () retorna um documento que atualizou o objeto incorporado, mas não é realmente salvo no mongodb. Por favor me ajude nisso.

@ Manojkumar91 você pode fornecer algum código que repros isso? Seria muito útil :)

Eu também encontrei isso, aqui está meu modelo:

/*jslint node:true */
"use strict";

var mongoose = require('mongoose'),
    Schema = mongoose.Schema;

var measurement = new Schema({
    name: {type: String, required: true},
    instruction: {type: String, required: true}
});

var size = new Schema({
    name: {type: String, required: true},
    lengths: [String]
});

var chartSchema = new Schema({
    name: {type: String, required: true },
    diagram: {type: String, required: true },
    description: {type: String, required: true},
    totalMeasurements: {type: Number, required: true},
    totalSizes: {type: Number, required: true},
    measurements: [measurement],
    sizes: [size]
});

module.exports = mongoose.model('chart', chartSchema);

Isso faz parte de um aplicativo muito maior, então estou incluindo apenas o filtro de ação do controlador relevante, mas posso falar com o resto em um sentido mais amplo. Abaixo está o código do filtro:

    module.exports = function (next) {
      var paramCounter = 1,
          outerLoopCounter = 0,
          existingSizes = this.chart.sizes.length,
          outerLoopEdge = this.chart.totalSizes,
          innerLoopCounter = 0,
          innerloopEdge = this.chart.totalMeasurements - 1,
          size = {name: null, lengths: [] };

      if (this.req.method && this.req.method === "POST") {
          for (outerLoopCounter;
                  outerLoopCounter < existingSizes;
                  outerLoopCounter = outerLoopCounter + 1) {

              this.chart.sizes[outerLoopCounter].name =
                  this.param("name_" + paramCounter);

              for (innerLoopCounter;
                      innerLoopCounter <= innerloopEdge;
                      innerLoopCounter = innerLoopCounter + 1) {
                  this.chart.sizes[outerLoopCounter].lengths[innerLoopCounter] =
                      this.param(this.chart.measurements[innerLoopCounter].name);
              }
              paramCounter = paramCounter + 1;
              innerLoopCounter = 0;
          }

        for (outerLoopCounter;
                outerLoopCounter < outerLoopEdge;
                outerLoopCounter = outerLoopCounter + 1) {
            size.name = this.param("name_" + paramCounter);
            for (innerLoopCounter;
                    innerLoopCounter < innerloopEdge;
                    innerLoopCounter = innerLoopCounter + 1) {
                size.lengths.push(
                    this.param(this.chart.measurements[innerLoopCounter].name
                               + "_" + paramCounter)
                );
            }
            this.chart.sizes.push(size);
            paramCounter = paramCounter + 1;
            innerLoopCounter = 0;
            size = { name: null, lengths: [] };
        }

        this.chart.save(function (err) {
            if (err) {
                console.log(err);
            }

            this.chart_info = "measurements for <strong>" + this.chart.name + "</strong> saved.";
            this.render("display");
        }.bind(this));
    } else {
        next();
    }
  };

Na verdade, antes de executar esse filtro, um filtro é chamado que cria uma estrutura de formulário dependente do estado com base na matriz bidimensional de medidas e tamanhos, então essa lista de elementos é analisada para fornecer atualizações. Tenho testado com dados fictícios e o gráfico fictício atual (tirado do console) tem a seguinte aparência:

> db.charts.find();
{ "_id" : ObjectId("553da6c3d3d0940a640e878c"), "name" : "Chart With Measurements", "diagram" : "http://res.cloudinary.com/harung71k/image/upload/v1430103747/nd4gipcxnykbbcpcztp9.jpg", "description" : "<p>This is a test of the new measurement methodology, it works.</p>", "totalMeasurements" : 4, "totalSizes" : 3, "sizes" : [ { "name" : "Small", "_id" : ObjectId("554183ed63c5945b73b8a8e7"), "lengths" : [ "1", "2", "3", "4" ] }, { "name" : "Medium", "_id" : ObjectId("554183ed63c5945b73b8a8e8"), "lengths" : [ "5", "6", "7", "8" ] }, { "name" : "Large", "_id" : ObjectId("554183ed63c5945b73b8a8e9"), "lengths" : [ "9", "10", "11", "12" ] } ], "measurements" : [ { "name" : "Fuzz", "instruction" : "<p>Fuzz Instructions</p>", "_id" : ObjectId("553dadd253eb9f996c68a381") }, { "name" : "Buzz", "instruction" : "<p>Buzz Instructions</p>", "_id" : ObjectId("553dadd253eb9f996c68a382") }, { "name" : "Beatles", "instruction" : "<p>Beatles Instructions</p>", "_id" : ObjectId("553dadd253eb9f996c68a383") }, { "name" : "Stones", "instruction" : "<p>Stones instructions</p>", "_id" : ObjectId("553ee7a09ff8c567004bd261") } ], "__v" : 3 }

Eu testei a matriz de tamanhos modificados antes de salvar, dado uma entrada de "1111" no slot de tamanhos [0] .lengths [0] e isso está aparecendo no documento quando inspecionado no Node, mas o salvamento está retornando exatamente este mesmo documento como pré-salvo.

Informe-nos se precisar de mais detalhes.

@ crispen-smith vou precisar de alguns esclarecimentos, há código demais para eu conseguir entender facilmente. Você pode habilitar o modo de depuração do mongoose com require('mongoose').set('debug', true); e postar a saída? Isso me mostra as consultas e gravações que estão sendo enviadas ao servidor.

Sim, é muito código, mas ao mesmo tempo ... não o suficiente para explicá-lo facilmente.

Portanto, executar o debug parece sugerir que o salvamento nem mesmo foi atingido, aqui está um salvamento com uma mudança de nome:

Mongoose: charts.findOne({ name: 'Chart With Measurements' }) { fields: undefined }  
Mongoose: charts.update({ _id: ObjectId("553da6c3d3d0940a640e878c"), __v: 3 }) { '$set': { 'sizes.0.name': 'Smaller' } } {} 

Aqui está executando a mesma sequência de operação com uma alteração em um nome e um valor da matriz de strings:

Mongoose: charts.findOne({ name: 'Chart With Measurements' }) { fields: undefined }  
Mongoose: charts.update({ _id: ObjectId("553da6c3d3d0940a640e878c"), __v: 3 }) { '$set': { 'sizes.0.name': 'Small' } } {} 

E, com apenas a mudança em uma variável dentro da matriz:

Mongoose: charts.findOne ({name: 'Chart With Measurements'}) {fields: undefined}
(Sim, é isso ... sem consulta de atualização)

Vou tentar corrigi-lo excluindo todo o subdoc de cada vez, pelo menos por enquanto ... Não preciso da integridade referencial ou dos ids de objeto para esse propósito específico.

--ATUALIZAR--
Tentei excluir e recarregar, mas também não parece funcionar. Embora eu não tenha rastreamento de saída para esta peça, parece que a exclusão também não aciona uma operação de back-end. Existe algum tipo de verificação suja que não está sendo acionada?

Alguma opção para reabrir este dado minhas notas acima?

@ crispen-smith, aqui está minha tentativa básica de reproduzir o problema como um script autônomo:

var mongoose = require('mongoose');
mongoose.set('debug', true);
var util = require('util');
var assert = require('assert');

mongoose.connect('mongodb://localhost:27017/gh1204');

var Schema = mongoose.Schema;

var measurement = new Schema({
    name: {type: String, required: true},
    instruction: {type: String, required: true}
});

var size = new Schema({
    name: {type: String, required: true},
    lengths: [String]
});

var chartSchema = new Schema({
    measurements: [measurement],
    sizes: [size]
});

var Chart = mongoose.model('gh1204', chartSchema);

Chart.create({}, function(error, chart) {
  assert.ifError(error);
  chart.sizes.push({ name: 'bacon', lengths: ['25'] });
  chart.save(function(error, chart) {
    assert.ifError(error);
    assert.equal(chart.sizes[0].lengths.length, 1);
    assert.equal(chart.sizes[0].lengths[0], '25');
    console.log('done');
    process.exit(0);
  });
});

Nenhum dado até agora, ele corre como esperado. Você pode modificar o exemplo acima para demonstrar o problema que está vendo? Não fui capaz de traduzir suas descrições em prosa em código.

Claro, vou olhar isso um pouco mais tarde esta noite.

Experimente?

/*jslint node:true */
"use strict";

var mongoose = require('mongoose');
mongoose.set('debug', true);
var util = require('util');
var assert = require('assert');

mongoose.connect('mongodb://localhost:27017/gh1204');

var Schema = mongoose.Schema;

var measurement = new Schema({
  name: {type: String, required: true},
  instruction: {type: String, required: true}
  });

var size = new Schema({
  name: {type: String, required: true},
  lengths: [String]
    });

var chartSchema = new Schema({
  measurements: [measurement],
  sizes: [size]
    });

var Chart = mongoose.model('gh1204', chartSchema);

Chart.create({}, function (error, chart) {
    assert.ifError(error);
    chart.sizes.push({ name: 'bacon', lengths: ['25'] });
    chart.save(function (error, chart) {
      assert.ifError(error);
     assert.equal(chart.sizes[0].lengths.length, 1);
     assert.equal(chart.sizes[0].lengths[0], '25');
     console.log('Created Index');

    chart.sizes[0].lengths[0] = "20";

  chart.save(function (error, chart) {
    assert.ifError(error);
    assert.equal(chart.sizes[0].lengths.length, 1);
    assert.equal(chart.sizes[0].lengths[0], '25');
    console.log('Created Index');
    process.exit(0);

    });
  });
});

Esta é a saída do meu console em meu console de nó:

Crispens-MacBook-Pro:mongooseTest crispensmith$ node index.js
Mongoose: gh1204.insert({ _id: ObjectId("5580d0c44d32b07971dfd281"), sizes: [], measurements: [], __v: 0 })   
Mongoose: gh1204.update({ __v: 0, _id: ObjectId("5580d0c44d32b07971dfd281") }) { '$set': { measurements: [] }, '$pushAll': { sizes: [ { name: 'bacon', _id: ObjectId("5580d0c44d32b07971dfd282"), lengths: [ '25' ] } ] }, '$inc': { __v: 1 } }  
Created Index
Mongoose: gh1204.update({ __v: 1, _id: ObjectId("5580d0c44d32b07971dfd281") }) { '$set': { measurements: [] }, '$inc': { __v: 1 } }  

/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:108
if (this.ended &&! this.hasRejectListeners ()) lançar a razão;
^
AssertionError: "20" == "25"
em EventEmitter.(/Users/crispensmith/Documents/mongooseTest/index.js:44:14)
em EventEmitter.(/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:175:45)
em EventEmitter.emit (events.js: 98: 17)
em Promise.safeEmit (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:81:21)
em Promise.fulfill (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:94:24)
em Promise.resolve (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/lib/promise.js:113:23)
no modelo.(/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/lib/document.js:1569:39)
em next_ (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/hooks-fixed/hooks.js:89:34)
em EventEmitter.fnWrapper (/ Users / crispensmith / Documents / mongooseTest / node_modules / mongoose / node_ modules / hooks-fixed / hooks.js: 171: 15)
em EventEmitter.(/ Users / crispensmith / Documents / mongooseTest / node_modules / mongoose / node_ modules / mpromise / lib / promessa.js: 175: 45)
em EventEmitter.emit (events.js: 98: 17)
em Promise.safeEmit (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:81:21)
em Promise.fulfill (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:94:24)
em p1.then.then.self.isNew (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/lib/model.js:254:27)
em newTickHandler (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:229:18)
em process._tickCallback (node.js: 442: 13)

(desculpe, estou tendo problemas para fazer isso em uma redução adequada, este é o melhor que eu pude fazer.)
E aqui está a saída do mongoDB:

  > db.gh1204.find();
  { "_id" : ObjectId("5580d0c44d32b07971dfd281"), "sizes" : [ { "name" : "bacon", "_id" : ObjectId("5580d0c44d32b07971dfd282"), "lengths" : [ "25" ] } ], "measurements" : [ ], "__v" : 2 }

Do ponto de vista do CRUD, a essência disso é que não há problema com Create ou Reads, mas as atualizações estão falhando.

TLDR;
No meu caso de uso, isso significa que, uma vez que um conjunto de comprimentos foi definido para uma medição, eles não podem ser editados. Estou trabalhando em um projeto de varejo de moda com dois casos de uso que exigirão matrizes dentro de subdocs. O primeiro caso de uso é que os usuários comuns terão seus próprios perfis e devem ser capazes de mantê-los. O segundo caso de uso é que a função administrativa pode criar gráficos personalizados para as listagens de diferentes tipos de produtos (partes superiores, inferiores etc.). Eu normalmente não esperaria que o perfil de administrador precisasse editar esses gráficos secundários, mas ainda seria bom tê-lo para esse caso de uso.

Em teoria, a funcionalidade atual realmente apresenta um objeto imutável agradável (acidental), mas a quantidade de trabalho envolvida em usá-lo como um imutável em vez de apenas editar não é trivial.

Eu vejo qual é o problema - caso clássico da primeira pergunta do FAQ do mangusto . O Mongoose não pode rastrear mudanças quando você define um índice de array diretamente sem algo como proxies ES6 ou ES7 Object.observe() . Usar

chart.sizes[0].lengths.set(0, "20");

ou

chart.sizes[0].lengths[0] = '20';
chart.markModified('sizes.0.lengths.0');

Ok, isso faz sentido. Estranhamente (e isso pode ser atribuído às minhas estratégias de pesquisa), só consegui encontrar funções que operavam 1x por salvamento e nenhuma delas caberia no caso de uso.

Eu vejo qual é o problema - caso clássico da primeira pergunta do FAQ do mangusto.

Obrigado, foi esse o problema no meu caso! :)

model.myArray[index] = anyValue

torna-se

model.myArray.set(index, anyValue)

Isso também foi um problema para mim, gostaria de ter visto este tópico mais cedo!

Se você não tiver certeza se o mangusto será notificado sobre as mudanças, você pode usar

doc.markModified('propChanged')
doc.save() // works

Resolvi meu problema com Array.set () também.
: +1:

Recebi o mesmo erro, este é o meu snippet de código
image
E este é o tronco do mangusto
image
Isso é o que eu recebo do carteiro
image

Algo errado?

++ EDIT ++

  1. Tentei usar Array.set, obteve o mesmo resultado

Acho que a melhor pergunta é se há algo certo. Você está fazendo um salvamento assíncrono no bloco if (req.body.invite != []) e, em seguida, modificando a matriz invited no retorno de chamada de resolução. A chamada nRoom.invited.push() sempre acontecerá após a segunda chamada save (). Além disso, req.body.invite != [] sempre será verdadeiro porque [] == [] é falso em JS.

@ vkarpov15 Haha Ok, obrigado pela resposta! 👍 Tenho muito que aprender :)

@peterkrieg Obrigado!

Olá, Estou tendo este problema ao atualizar documentos profundamente incorporados. Alguém tem algum conselho? Meu código está em:

https://stackoverflow.com/questions/51426326/updating-deeply-embedded-documents-with-mongodb-2-6-12

@thehme qual versão do mangusto você está usando?

nesta linha, {$set: {["dT." + index +".ts." + i + ".th"]: newValue}} , os colchetes parecem fora do lugar para mim. faz alguma diferença usar {$set: { "dT." + index +".ts." + i + ".th": newValue } } vez disso?

$ set docs mostram apenas uma string

sinta-se à vontade para se juntar a nós no Gitter.im ou no Slack para falar sobre isso em tempo real 👍

Estou usando o 5.5.11 e ainda estou tendo esse problema. Tentei todas as soluções propostas neste tópico sem sorte. O layout do meu documento é assim:

{
  myObj: {
    myArr: [{ key: "value" }]
  }
}

myDoc.myObj.myArr.push({ key: "value2" });
// debugging myDoc shows new embedded doc
await myDoc.save();
// console shows new embedded doc is gone

Editar: atualizado para o mais recente (5.7.6) e ainda tendo o problema.

O script abaixo funciona bem no Mongoose 5.7.6. Abra um novo problema e siga o modelo de problema.

const mongoose = require('mongoose');

run().catch(err => console.log(err));

async function run() {
  await mongoose.connect('mongodb://localhost:27017/test', {
    useNewUrlParser: true,
    useUnifiedTopology: true
  });
  await mongoose.connection.dropDatabase();

  const schema = mongoose.Schema({ 
    myObj: { myArr: [{ key: String }] }
  });
  const Model = mongoose.model('Test', schema);


  await Model.create({ myObj: { myArr: [{ key: 'value' }] } });
  const myDoc = await Model.findOne();

  myDoc.myObj.myArr.push({ key: "value2" });
  await myDoc.save();

  console.log(myDoc.myObj.myArr);

  const saved = await Model.findOne();
  console.log(myDoc.myObj.myArr);
}
Esta página foi útil?
0 / 5 - 0 avaliações