sql >> Base de Datos >  >> NoSQL >> MongoDB

FindAndUpdate Cómo verificar si el documento se actualizó realmente

La única forma realmente confiable de ver si se aplicó una actualización para algo como $pull es básicamente comprobar el documento devuelto y ver si los datos que pretendía $pull todavía está allí o no.

Eso es para cualquiera de los "findAndUpdate" variedad de acciones, y hay una razón válida para eso, además de ser el caso de que un simple .update() en realidad le dirá "confiablemente" si la modificación se realizó de hecho.

Para recorrer los casos:

Comprueba el contenido devuelto

Básicamente, esto implica mirar la matriz en el documento devuelto para ver si lo que solicitamos eliminar está realmente allí:

var pullId = "5961de06ea264532c684611a";

Office.findByIdAndUpdate(1,
  { "$pull": { "branches": { "_id": pullId } } },
  { "new": true }
).then(office => {
  // Check if the supplied value is still in the array
  console.log(
    "Still there?: %s",
    (office.branches.find( b => b._id.toHexString() === pullId))
      ? true : false
  );
}).catch(err => console.error(err))

Usamos .toHexString() para comparar el valor real de un ObjectId ya que JavaScript simplemente no hace "igualdad" con "Objetos". Verificaría tanto "izquierda" como "derecha" si proporciona algo que ya fue "transmitido" a un ObjectId valor, pero en este caso sabemos que la otra entrada es una "cadena".

Simplemente use .update(), "Es confiable"

El otro caso aquí a considerar pone en duda si "realmente necesita" los datos modificados devueltos de todos modos. Porque el .update() método, devolverá de manera confiable un resultado que le indicará si algo se modificó realmente:

Office.update(
  { "_id": 1 },
  { "$pull": { "branches": { "_id": pullId } } },
).then(result => {
  log(result);
}).catch(err => console.error(err))

Donde result aquí se verá como:

{
  "n": 1,
  "nModified": 1,        // <--- This always tells the truth, and cannot lie!
  "opTime": {
    "ts": "6440673063762657282",
    "t": 4
  },
  "electionId": "7fffffff0000000000000004",
  "ok": 1
}

Y en el que el nModified es un indicador "verdadero" de si algo "actualizó realmente". Por lo tanto, si es 1 luego el $pull en realidad tuvo un efecto, pero cuando 0 no se eliminó nada de la matriz y no se modificó nada.

Esto se debe a que el método en realidad usa la API actualizada, que tiene resultados confiables que indican modificaciones reales. Lo mismo se aplicaría a algo como $set que en realidad no cambió el valor porque el valor proporcionado era igual al que ya existía en el documento.

¡Encuentra y modifica mentiras!

El otro caso aquí en el que podría pensar al mirar de cerca la documentación es inspeccionar realmente el "resultado sin procesar" y ver si el documento se modificó o no. De hecho, hay un indicador en la especificación para esto.

El problema es (además de requerir más trabajo con Promesas) que el resultado no es realmente veraz:

var bogusId = "5961de06ea264532c684611a"; // We know this is not there!

Promise((resolve,reject) => {
  Office.findByIdAndUpdate(1,
    { "$pull": { "branches": { "_id": bogusId } } },
    { "new": true, "passRawResult" },
    (err,result,raw) => {        // We cannot pass multiple results to a Promise
      if (err) reject(err);
      resolve({ result, raw });   // So we wrap it!
    }
  )
})
.then(response => log(response.raw))
.catch(err => console.error(err));

El problema aquí es que incluso cuando "sabemos" que esto no debería modificarse, la respuesta dice lo contrario:

{
  "lastErrorObject": {
    "updatedExisting": true,
    "n": 1                     // <--- LIES! IT'S ALL LIES!!!
  },
  "value": {
    "_id": 1,
    "name": "My Office",
    "branches": [
      {
        "address": "Third address",
        "isPrincipal": false,
        "_id": "5961de06ea264532c6846118"
      }
    ],
    "__v": 0
  },
  "ok": 1,
  "_kareemIgnore": true
}

Entonces, incluso después de todo ese trabajo para sacar el "tercer" argumento de la respuesta de devolución de llamada, aún no recibimos la información correcta sobre la actualización.

Conclusión

Entonces, si desea hacer esto de manera "confiable" con una sola solicitud (y no puede hacerlo de manera confiable con múltiples solicitudes ya que el documento podría cambiar ¡entre! ) entonces tus dos opciones son:

  1. Verifique el documento devuelto para ver si los datos que deseaba eliminar todavía están allí.

  2. Olvídese de devolver un documento y confíe en que .update() siempre te dice la "verdad";)

Cuál de estos use depende del patrón de uso de la aplicación, pero esas son las dos formas diferentes de devolver un resultado "confiable".

Un poco de listado

Entonces, para estar seguro, aquí hay una lista que repasa todos los ejemplos y demuestra lo que realmente devuelven:

const async = require('async'),
      mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

mongoose.connect('mongodb://localhost/test');

const branchesSchema = new Schema({
  address: String,
  isPrincipal: Boolean
});

const officeSchema = new Schema({
  _id: Number,
  name: String,
  branches: [branchesSchema]
},{ _id: false });

const Office = mongoose.model('Office', officeSchema);

function log(data) {
  console.log(JSON.stringify(data,undefined,2))
}

const testId = "5961a56d3ffd3d5e19c61610";

async.series(
  [
    // Clean data
    (callback) =>
      async.each(mongoose.models,(model,callback) =>
        model.remove({},callback),callback),

    // Insert some data and pull
    (callback) =>
      async.waterfall(
        [
          // Create and demonstrate
          (callback) =>
            Office.create({
              _id: 1,
              name: "My Office",
              branches: [
                {
                  address: "Some street, that avenue",
                  isPrincipal: true
                },
                {
                  address: "Another address",
                  isPrincipal: false
                },
                {
                  address: "Third address",
                  isPrincipal: false
                }
              ]
            },callback),

          // Demo Alternates
          (office,callback) =>
            async.mapSeries(
              [true,false].map((t,i) => ({ t, branch: office.branches[i] })),
              (test,callback) =>
                (test.t)
                  ? Office.findByIdAndUpdate(office._id,
                      { "$pull": { "branches": { "_id": test.branch._id } } },
                      { "new": true , "passRawResult": true },
                      (err,result,raw) => {
                        if (err) callback(err);
                        log(result);
                        log(raw);
                        callback();
                      })
                  : Office.findByIdAndUpdate(office._id,
                      { "$pull": { "branches": { "_id": test.branch._id } } },
                      { "new": true } // false here
                    ).then(result => {
                      log(result);
                      console.log(
                        "Present %s",
                        (result.branches.find( b =>
                          b._id.toHexString() === test.branch._id.toHexString() ))
                          ? true : false
                      );
                      callback();
                    }).catch(err => callback(err)),
              callback
            )
        ],
        callback
      ),

    // Find and demonstate fails
    (callback) =>
      async.waterfall(
        [
          (callback) => Office.findOne({},callback),

          (office,callback) =>
            async.eachSeries([true,false],(item,callback) =>
              (item)
                ? Office.findByIdAndUpdate(office._id,
                    { "$pull": { "branches": { "_id": testId } } },
                    { "new": true, "passRawResult": true },
                    (err,result,raw) => {
                      if (err) callback(err);
                      log(result);
                      log(raw);
                      callback();
                    }
                  )
                : Office.findByIdAndUpdate(office._id,
                    { "$pull": { "branches": { "_id": testId } } },
                    { "new": true }
                  ).then(result => {
                    console.log(result);
                    console.log(
                      "Present %s",
                      (result.branches.find( b =>
                        b._id.toHexString() === office.branches[0]._id.toHexString()))
                        ? true : false
                    );
                    callback();
                  })
                  .catch(err => callback(err)),
              callback)

        ],
        callback
      ),

    // Demonstrate update() modified shows 0
    (callback) =>
      Office.update(
        {},
        { "$pull": { "branches": { "_id": testId } } }
      ).then(result => {
        log(result);
        callback();
      })
      .catch(err => callback(err)),

    // Demonstrate wrapped promise
    (callback) =>
      Office.findOne()
        .then(office => {
          return new Promise((resolve,reject) => {
            Office.findByIdAndUpdate(office._id,
              { "$pull": { "branches": { "_id": testId } } },
              { "new": true, "passRawResult": true },
              (err,result,raw) => {
                if (err) reject(err);
                resolve(raw)
              }
            );
          })
        })
        .then(office => {
          log(office);
          callback();
        })
        .catch(err => callback(err))

  ],
  (err) => {
    if (err) throw err;
    mongoose.disconnect();
  }
);

Y la salida que produce:

Mongoose: offices.remove({}, {})
Mongoose: offices.insert({ _id: 1, name: 'My Office', branches: [ { address: 'Some street, that avenue', isPrincipal: true, _id: ObjectId("5961e5211a73e8331b44d74b") }, { address: 'Another address', isPrincipal: false, _id: ObjectId("5961e5211a73e8331b44d74a") }, { address: 'Third address', isPrincipal: false, _id: ObjectId("5961e5211a73e8331b44d749") } ], __v: 0 })
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961e5211a73e8331b44d74b") } } }, { new: true, passRawResult: true, upsert: false, remove: false, fields: {} })
{
  "_id": 1,
  "name": "My Office",
  "__v": 0,
  "branches": [
    {
      "address": "Another address",
      "isPrincipal": false,
      "_id": "5961e5211a73e8331b44d74a"
    },
    {
      "address": "Third address",
      "isPrincipal": false,
      "_id": "5961e5211a73e8331b44d749"
    }
  ]
}
{
  "lastErrorObject": {
    "updatedExisting": true,
    "n": 1
  },
  "value": {
    "_id": 1,
    "name": "My Office",
    "branches": [
      {
        "address": "Another address",
        "isPrincipal": false,
        "_id": "5961e5211a73e8331b44d74a"
      },
      {
        "address": "Third address",
        "isPrincipal": false,
        "_id": "5961e5211a73e8331b44d749"
      }
    ],
    "__v": 0
  },
  "ok": 1,
  "_kareemIgnore": true
}
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961e5211a73e8331b44d74a") } } }, { new: true, upsert: false, remove: false, fields: {} })
{
  "_id": 1,
  "name": "My Office",
  "__v": 0,
  "branches": [
    {
      "address": "Third address",
      "isPrincipal": false,
      "_id": "5961e5211a73e8331b44d749"
    }
  ]
}
Present false
Mongoose: offices.findOne({}, { fields: {} })
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, { new: true, passRawResult: true, upsert: false, remove: false, fields: {} })
{
  "_id": 1,
  "name": "My Office",
  "__v": 0,
  "branches": [
    {
      "address": "Third address",
      "isPrincipal": false,
      "_id": "5961e5211a73e8331b44d749"
    }
  ]
}
{
  "lastErrorObject": {
    "updatedExisting": true,
    "n": 1
  },
  "value": {
    "_id": 1,
    "name": "My Office",
    "branches": [
      {
        "address": "Third address",
        "isPrincipal": false,
        "_id": "5961e5211a73e8331b44d749"
      }
    ],
    "__v": 0
  },
  "ok": 1,
  "_kareemIgnore": true
}
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, { new: true, upsert: false, remove: false, fields: {} })
{ _id: 1,
  name: 'My Office',
  __v: 0,
  branches:
   [ { address: 'Third address',
       isPrincipal: false,
       _id: 5961e5211a73e8331b44d749 } ] }
Present true
Mongoose: offices.update({}, { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, {})
{
  "n": 1,
  "nModified": 0,
  "opTime": {
    "ts": "6440680872013201413",
    "t": 4
  },
  "electionId": "7fffffff0000000000000004",
  "ok": 1
}
Mongoose: offices.findOne({}, { fields: {} })
Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, { new: true, passRawResult: true, upsert: false, remove: false, fields: {} })
{
  "lastErrorObject": {
    "updatedExisting": true,
    "n": 1
  },
  "value": {
    "_id": 1,
    "name": "My Office",
    "branches": [
      {
        "address": "Third address",
        "isPrincipal": false,
        "_id": "5961e5211a73e8331b44d749"
      }
    ],
    "__v": 0
  },
  "ok": 1,
  "_kareemIgnore": true
}