Mongoose: Model.save () no guarda matrices incrustadas

Creado en 10 nov. 2012  ·  26Comentarios  ·  Fuente: Automattic/mongoose

He anidado matrices de documentos, y cuando las cambio y hago un .save () en el modelo, no guarda los cambios en el documento anidado.

Aquí están mis modelos y esquemas (algunos omitidos por brevedad):

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 () llamado:

{ 
     __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: [] 
}

El mismo objeto con los votos anidados justo antes de que se llame a event.save() .

{
   "__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":[]
}

Cuando se llama a event.save (), no se genera ningún error, pero el esquema de votos anidados dentro del esquema de eventos anidados no se guarda realmente. Si utilizo la misma lógica general en el objeto de evento de nivel superior para guardar un voto, funciona.

Cuando miré brevemente el código, parece que se supone que .save () es un atajo tanto para guardar nuevos objetos como para actualizar los que ya existen.

Mi corazonada es Model.prototype._delta no es lo suficientemente profundo como para capturar todos los objetos anidados, https://github.com/LearnBoost/mongoose/blob/master/lib/model.js#L529

Comentario más útil

Veo cuál es el problema: el caso clásico de la primera pregunta de las preguntas frecuentes sobre mangostas.

Gracias, fue este problema en mi caso! :)

model.myArray[index] = anyValue

se convierte en

model.myArray.set(index, anyValue)

Todos 26 comentarios

Incluya también el código que está utilizando para reproducir el problema (en el que está manipulando el documento antes de guardarlo, etc.)

Claro, lo publiqué en resumen para evitar un muro de código aquí, https://gist.github.com/4055392

Incluí tanto guardar un voto en el evento de nivel superior (en funcionamiento) como guardarlo dentro de un evento anidado (que no funciona). Como puede ver, agregan o actualizan un voto y luego lo guardan de manera similar.

Intente arreglar su referencia EventSchema usada en 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
})


cierre, sin respuesta. vuelva a abrir si es necesario.

Tengo el mismo problema en el que .save () devuelve un documento que ha actualizado el objeto incrustado pero en realidad no se guarda en mongodb. Por favor ayúdame con esto.

@ Manojkumar91 ¿ puede proporcionar algún código que repros esto? Sería muy útil :)

También me he encontrado con esto, aquí está mi 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);

Esto es parte de una aplicación mucho más grande, por lo que solo incluyo el filtro de acción del controlador relevante, pero puedo hablar con el resto en un sentido más amplio. A continuación se muestra el código de 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();
    }
  };

En efecto, antes de ejecutar este filtro se llama a un filtro que crea una estructura de formulario dependiente del estado basada en la matriz bidimensional de medidas y tamaños, luego esta lista de elementos se analiza para proporcionar actualizaciones. He estado probando con datos ficticios y el gráfico ficticio actual (tomado de la consola) se ve así:

> 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 }

Probé la matriz de tamaños modificados justo antes de guardar, dada una entrada de "1111" en la ranura de tamaños [0] .lengths [0] y esto se muestra en el documento cuando se inspecciona en el nodo, pero el guardado devuelve exactamente este mismo documento como pre-guardado.

Por favor, avíseme si necesita más detalles.

@ crispen-smith Voy a necesitar una aclaración, hay demasiado código para que pueda entenderlo fácilmente. ¿Puede habilitar el modo de depuración de mangosta con require('mongoose').set('debug', true); y publicar la salida? Eso me muestra las consultas y escrituras que se envían al servidor.

Sí, es mucho código, pero al mismo tiempo ... no es suficiente para explicarlo fácilmente.

Entonces, ejecutar la depuración parece sugerir que el guardado ni siquiera se golpea, aquí hay un guardado con un cambio de nombre:

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

Aquí se ejecuta la misma secuencia de operación con un cambio en un nombre y un valor de la matriz de cadenas:

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

Y, con solo un cambio en una variable dentro de la matriz:

Mangosta: charts.findOne ({nombre: 'Gráfico con medidas'}) {campos: indefinido}
(Sí, eso es todo ... sin consulta de actualización)

Voy a intentar parchearlo eliminando el subdocumento completo cada vez, al menos por el momento ... No necesito integridad referencial o los identificadores de objeto para este propósito en particular.

--ACTUALIZAR--
Intenté eliminar y volver a cargar, pero eso tampoco parece funcionar. Si bien no tengo seguimiento de salida para esta pieza, parece que la eliminación tampoco desencadena una operación de back-end. ¿Existe algún tipo de verificación sucia que no se esté activando?

¿Alguna opción para reabrir esto dadas mis notas anteriores?

@ crispen-smith aquí está mi intento básico de reproducir el problema como un script independiente:

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);
  });
});

Hasta ahora no hay dados, funciona como se esperaba. ¿Puede modificar el ejemplo anterior para demostrar el problema que está viendo? No he podido traducir sus descripciones en prosa a código.

Claro, lo veré un poco más tarde esta noche.

¿Probar esto?

/*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);

    });
  });
});

Aquí está la salida de mi consola en mi consola de nodo:

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 ()) arrojar razón;
^
AssertionError: "20" == "25"
en EventEmitter.(/Users/crispensmith/Documents/mongooseTest/index.js:44:14)
en EventEmitter.(/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:175:45)
en EventEmitter.emit (events.js: 98: 17)
en Promise.safeEmit (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:81:21)
en Promise.fulfill (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:94:24)
en Promise.resolve (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/lib/promise.js:113:23)
en el modelo.(/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/lib/document.js:1569:39)
en next_ (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/hooks-fixed/hooks.js:89:34)
en EventEmitter.fnWrapper (/ Users / crispensmith / Documents / mongooseTest / node_modules / mongoose / node_ modules / hooks-fixed / hooks.js: 171: 15)
en EventEmitter.(/ Users / crispensmith / Documents / mongooseTest / node_modules / mongoose / node_ modules / mpromise / lib / promise.js: 175: 45)
en EventEmitter.emit (events.js: 98: 17)
en Promise.safeEmit (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:81:21)
en Promise.fulfill (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:94:24)
en p1.then.then.self.isNew (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/lib/model.js:254:27)
en newTickHandler (/Users/crispensmith/Documents/mongooseTest/node_modules/mongoose/node_modules/mpromise/lib/promise.js:229:18)
en process._tickCallback (node.js: 442: 13)

(Lo siento, me lo he pasado genial para conseguir la rebaja adecuada, esto es lo mejor que pude hacer).
Y aquí está la salida de mongoDB:

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

Desde la perspectiva de CRUD, la esencia de esto es que no hay ningún problema con las actualizaciones Create o Leads, put.

TLDR;
En mi caso de uso, esto significa que una vez que se ha definido un conjunto de longitudes para una medida, no se pueden editar. Estoy trabajando en un proyecto de venta minorista de moda con dos casos de uso que requerirán matrices dentro de los subdocumentos. El primer caso de uso es que los usuarios vanilla tendrán su propio perfil y deberían poder mantenerlos. El segundo caso de uso es que el rol de administrador puede crear gráficos personalizados para los listados para diferentes tipos de productos (superiores, inferiores, etc.). Normalmente, no esperaría que el perfil de administrador necesite editar estos gráficos secundarios, pero aún así sería bueno tenerlos para ese caso de uso.

En teoría, la funcionalidad actual en realidad presenta un objeto inmutable agradable (accidental), pero la cantidad de trabajo involucrado en usarlo como algo inmutable en lugar de solo edición no es trivial.

Veo cuál es el problema: el caso clásico de la primera pregunta de las Object.observe() . Usar

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

o

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

Está bien, eso tiene sentido. Curiosamente, (y esto puede atribuirse a mis estrategias de búsqueda) solo pude encontrar funciones que funcionaban 1x por guardado, y ninguna de ellas habría encajado en el caso de uso.

Veo cuál es el problema: el caso clásico de la primera pregunta de las preguntas frecuentes sobre mangostas.

Gracias, fue este problema en mi caso! :)

model.myArray[index] = anyValue

se convierte en

model.myArray.set(index, anyValue)

Esto también fue un problema para mí, ¡desearía haber visto este hilo antes!

Si alguna vez no está seguro de que mangosta reciba notificaciones de cambios, puede usar

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

También resolví mi problema con Array.set ().
: +1:

Obtuve el mismo error, este es mi fragmento de código
image
Y este es el tronco de la mangosta
image
Esto es lo que recibo del cartero
image

¿Nada malo?

++ EDITAR ++

  1. Intenté usar Array.set, obtengo el mismo resultado

Creo que la mejor pregunta es si hay algo correcto. Está haciendo un guardado asincrónico en el bloque if (req.body.invite != []) y luego modificando la matriz invited en la devolución de llamada de resolución. La llamada nRoom.invited.push() siempre ocurrirá después de la segunda llamada save (). Además, req.body.invite != [] siempre será verdadero porque [] == [] es falso en JS.

@ vkarpov15 Haha Ok, ¡gracias por la respuesta! 👍 Tengo muchas cosas que aprender :)

@peterkrieg ¡Gracias!

Hola, tengo este problema al actualizar documentos profundamente incrustados, ¿alguien tiene algún consejo? Mi código está en:

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

@thehme ¿qué versión de mangosta estás usando?

en esta línea, {$set: {["dT." + index +".ts." + i + ".th"]: newValue}} , los corchetes me parecen fuera de lugar. ¿Hay alguna diferencia en usar {$set: { "dT." + index +".ts." + i + ".th": newValue } } lugar?

$ set docs muestran solo una cadena

siéntete libre de unirte a nosotros en Gitter.im o Slack para hablar sobre esto en tiempo real 👍

Estoy usando 5.5.11 y sigo teniendo este problema. Probé todas las soluciones propuestas en este hilo sin suerte. El diseño de mi documento se ve así:

{
  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: actualizado a la última versión (5.7.6) y aún tiene el problema.

El siguiente script funciona bien en Mongoose 5.7.6. Abra un nuevo problema y siga la plantilla de problemas.

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);
}
¿Fue útil esta página
0 / 5 - 0 calificaciones