Mongoose: Un tableau vide est enregistré lorsqu'une propriété fait référence à un schéma

Créé le 6 févr. 2013  ·  39Commentaires  ·  Source: Automattic/mongoose

var FooSchema = new Schema({});
var Foo = mongoose.model("Foo", FooSchema);
var BarSchema = new Schema({
    foos: [Foo.schema]
});
var Bar = mongoose.model("Bar", BarSchema);

var b = new Bar();
b.save();

Cela créera un tableau vide de b.foos au lieu de simplement laisser la propriété indéfinie.

enhancement

Commentaire le plus utile

@antonioaltamura J'ai oublié ce problème, le ci-dessous fonctionne en mangouste 4.6 :

const CollectionSchema = new Schema({
 field1: { type: [String], default: void 0 }, // <-- override the array default to be undefined
});

const Collection = mongoose.model('test', CollectionSchema);

Collection.create({}).
  then(doc => { console.log(doc); return doc; }).
  then(() => { process.exit(0); });

Tous les 39 commentaires

C'est par conception. Toutes les valeurs par défaut sont enregistrées afin que la vue des applications mangouste du document soit identique à une vue des applications non mangouste.

Ok, comment faire pour que la valeur par défaut soit nulle ? ;-) Le cas d'utilisation ici est qu'il est coûteux en mongo de demander tous les bars qui ont des foos vides. Je dois ajouter une autre propriété pour suivre cet état, ce qui complique encore plus mon code.

Je vois. Cela vous permettra d'ignorer l'enregistrement d'objets vides dans de nouveaux documents :

BarSchema.pre('save', function (next) {
  if (this.isNew && 0 === this.foos.length) {
    this.foos = undefined;                                                                                                                                   
  }
  next();
})

Doux. C'est un assez bon travail autour pour moi.

Cette solution de contournement est-elle toujours censée fonctionner ? (mangouste 3.8.3 ici)

Mathieumg : ça ne marche pas pour moi (3.8.15)

Oui, je pense que cette question mérite une certaine attention. (C'est assez mineur, mais
Je suis intéressé à utiliser pre pour d'autres choses :) )

Le lun 25 août 2014 à 16:49, Mathieu M-Gosselin <
[email protected]> a écrit :

Peut-être devrais-je ouvrir un nouveau numéro pour que cela soit remarqué?

-
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/LearnBoost/mongoose/issues/1335#issuecomment -53328195
.

+1
Le travail préalable ne fonctionne pas pour la 3.8.15
la seule alternative, je pense, est d'avoir null ou false pour simplifier la requête, mais cela ajoute également des octets et a un effet significatif sur une grande base de données.

        this.foos = null;

@gaurang171 @Nepoxx pouvez-vous tous me fournir un code qui reproduit cela ? La solution de contournement fonctionne bien dans nos cas de test (voir cas de test )

C'est toujours un problème pour moi en utilisant 4.0.1.

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

var barSchema = new mongoose.Schema({
  baz: String
});

var fooSchema = new mongoose.Schema({
  bars: [barSchema]
});

var Foo = mongoose.model('Foo', fooSchema);

var foo = new Foo();
console.log(foo); // { _id: 55256e20e3c38434687034fb, bars: [] }

foo.save(function(err, foo2) {
  console.log(foo2); // { __v: 0, _id: 55256e20e3c38434687034fb, bars: [] }

  foo2.bars = undefined;
  foo2.save(function(err, foo3) {
    console.log(foo3); // { __v: 0, _id: 55256e20e3c38434687034fb, bars: undefined }

    Foo.findOne({ _id: foo3._id }, function(err, foo4) {
      console.log(foo4); // { _id: 55256e20e3c38434687034fb, __v: 0, bars: [] }

      mongoose.disconnect();
    });
  });
});

@viking Cela n'a jamais été "réparé". Une solution de contournement a été fournie . Vous devriez tester pour voir si cette solution de contournement fonctionne toujours car il existe des informations contradictoires à ce sujet. =)

J'ai déjà une solution de contournement en place. Je suis intéressé de savoir si ce comportement va changer ou non.

Désolé, vous n'avez pas dit que vous vouliez savoir si le comportement allait changer ou non.

En fait, je ne sais pas pourquoi ce problème a été rouvert par @vkarpov15 , ce qui

Oui, ce comportement ne changera pas dans un proche avenir. J'ai rouvert ce problème afin que je savais l'enquêter quand j'en ai eu l'occasion, car je m'assure de comprendre ce qui se passe avec chaque commentaire qui arrive sur le github de la mangouste et c'est celui que je n'ai pas eu de bonne réponse au sommet de ma tête. En regardant de plus près, nous ne pouvons pas vraiment changer cela dans un proche avenir à cause de semver, mais quelque chose à étudier si c'est un problème pour beaucoup d'utilisateurs.

J'ai utilisé cette solution de contournement avec une torsion parce que parfois vous VOULEZ écrire un tableau vide et parfois non.

Ma présave ressemble donc à ceci :

        var schema = mongoose.Schema({
                   ...
           "children": [ String ]
                   ...
            });
        // turn off the saving of empty children if there are no children in the schema.
        schema.pre('save', function (next) {
            if (this.isNew) {
                if (this.children.length == 0) {
                    this.children = undefined;       
                }
                else if (this.children.length == 1 && this.children[0] == null) {
                    this.children = [];
                }                                                                                                                    
            }
            next();
        });
        var model = mongoose.model('MyDocument', schema);

Et puis pour écrire des enfants vides j'utilise ceci :

idea.children = [null];

Considérant que ceci est dépouillé:

idea.children = [];

Cela pourrait ne pas fonctionner pour vous, mais vous devriez être en mesure de proposer un autre type de schéma.

C'est un très gros problème lorsque vous avez des tableaux à l'intérieur d'objets imbriqués.

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

var quxSchema = new mongoose.Schema({ qux: String });
var fooSchema = new mongoose.Schema({ foo: { bar: { baz: [ quxSchema ] } } });
var Foo = mongoose.model('Foo', fooSchema);

var foo = new Foo({foo: undefined})
foo.save(function(err, foo) {
  console.log(JSON.stringify(foo)); // {"__v":0,"_id":"557ae56480f047fd4ff4ab26","foo":{"bar":{"baz":[]}}}
  Foo.find({ _id: foo._id }, function(err, foos) {
    console.log(JSON.stringify(foos[0])); // {"_id":"557ae56480f047fd4ff4ab26","__v":0,"foo":{"bar":{"baz":[]}}}
  });
});

Ici, je m'attends foo ce que

Mais une pré-sauvegarde ne peut-elle pas vous aider ici ? plutôt que d'avoir {foo: undefined}, vous pouvez utiliser un type unique de foo pour signifier un foo nul, puis supprimer le foo dans le présave.

Moche quand même.

Je viens de rencontrer le même problème que @viking . avez-vous trouvé une solution de contournement utilisable?

Je l'ai essayé avec la méthode de sauvegarde "pré"... mais cela se produit encore parfois, je ne sais pas combien de cas d'utilisation différents je dois couvrir pour y parvenir. Mon plus gros problème est que le JSON doit en fait fournir un objet et non un tableau. il s'agit d'une exception fatale pour mon analyseur json qui essaie de mapper ces éléments sur des objets java.

Exemple:

var subA = mongoose.Schema({
     name: String,
     a: String
});

var subB = mongoose.Schema({
     name: String,
     b: String
});

var A = mongoose.Schema({
name: String,
filter: {
        lastupdate  : { type: Date, default: Date.now },
        subA: [{type: Schema.Types.ObjectId, ref: 'subA', unique: true}],
        subB: [{type: Schema.Types.ObjectId, ref: 'subB', unique: true}]
    }
});

var ModelA = mongoose.model('A', A);

var obj = new modelA();
obj.save();

... cela donne :

{
 filter: []
}

mais il ne devrait tout simplement pas être là, ou au moins être un objet et non un tableau vide :

{
 filter: {} 
}

quelqu'un des idées comment résoudre cela?

Cordialement

Je pense que vous pouvez faire en sorte que cela fonctionne, évidemment pas avec le présave que j'ai posté, car cela impliquait que les enfants soient un tableau et non un objet. Je n'ai pas essayé cela, mais ne serait-il pas possible d'utiliser réellement un objet vide "duck typing" pour signifier un VRAI vide?

Donc votre présave pourrait être :

if (this.isNew) {
    if (typeof(this.filter.empty) != "undefined") {
        this.filter = {};
    }
}

Ensuite, pour créer un filtre vide, vous pouvez utiliser :

var obj = new modelA();
obj.filter = { empty: true };
obj.save();

Notez que cela n'a pas d'importance si le schéma n'a pas "vide" dedans, il ne le fait jamais en mangouste.

D'une manière ou d'une autre, j'ai réussi à surmonter le problème. J'ai en fait fait quelque chose de très similaire. Je viens de voir ta réponse et je voulais te dire merci :)

Cordialement
Simon

J'avais beaucoup de problèmes avec cette solution lorsqu'il y avait des schémas imbriqués avec des champs optionnels Array . J'ai résolu ce problème en créant un nouveau type :

optional_array = 
  type: Mixed
  validate: 
    validator: (v) ->
      return v instanceof Array
    message: '{VALUE} needs to be an array.'

puis en définissant tous mes champs sur optional_array au lieu de Array .

@simllll J'utilise un espace réservé null pour les objets manquants, puis supprime le null avant d'utiliser l'objet.

Désolé, mais c'est une conception horrible. Vous ne devriez pas décider en mon nom si je veux que ce soit un tableau vide ou pas du tout défini. Prévoyez au moins une option pour désactiver cette fonctionnalité.

D'accord avec @ifeltsweet

Après des mois, existe-t-il une bonne solution sans pré-crochet ? J'ai des dizaines de champs de tableau, je devrais gérer un par un dans un pré-hook...

@antonioaltamura J'ai oublié ce problème, le ci-dessous fonctionne en mangouste 4.6 :

const CollectionSchema = new Schema({
 field1: { type: [String], default: void 0 }, // <-- override the array default to be undefined
});

const Collection = mongoose.model('test', CollectionSchema);

Collection.create({}).
  then(doc => { console.log(doc); return doc; }).
  then(() => { process.exit(0); });

@ vkarpov15 votre correctif ne fonctionne pas pour moi sur la mangouste 4.7.0. Fonctionne toujours bien pour vous ? Il n'inclut pas la valeur sur la création d'un nouvel objet, mais lorsque je fais un Model.save, il essaie de l'ajouter à nouveau.

@cpup22 pouvez-vous fournir un exemple de code ? Le script référencé fonctionne bien pour moi sur 4.7.1.

Je pense que la solution de contournement de https://github.com/Automattic/mongoose/issues/2363#issuecomment -171988413 est beaucoup plus propre. Il permet d'utiliser par défaut null même lorsque default: null échoue :

const CollectionSchema = new Schema({
  field1: { type: [String] },
});
CollectionSchema.methods.postconstructed = function () {
  this.field1 = null;
};
CollectionSchema.queue('postconstructed');

const Collection = mongoose.model('test', CollectionSchema);

Collection.create({}).then(doc => { console.log(doc); process.exit(0) });

Peu importe, cela ne fonctionne pas lors de la transmission d'une valeur (par exemple, new Collection({ field1: [] }) obtient field1 === null . Si seulement il y avait un hook de construction qui s'est produit avant que les données transmises ne soient appliquées et après la valeur par défaut un tableau vide est créé. Ou ce bogue a été corrigé pour autoriser default: null pour les tableaux.

Oh, on dirait que ça marche :

const CollectionSchema = new Schema({
  field1: { type: [String], default: () => null },
});

La chose de type tableau respecte les fonctions plus sincèrement que les valeurs. Cela n'a aucun problème avec l'écrasement d'une valeur de tableau null , [] transmise ou non vide via Collection.create() ou new Collection() .

@ vkarpov15 bonjour, donc même si je change la valeur de la clé pour le type array en null dans mon schéma mongoose avant que mongo db n'enregistre un document, et lorsque le tableau est vide, il l'enregistre en tant que key = null.
tu as des solutions

@vikramkalta, veuillez fournir des exemples de code, les conversions de prose en code sont sujettes aux erreurs.

Salut, j'utilise la mangouste 4.6.5 et je continue à enregistrer un tableau vide avec ces solutions.

services: [{ type: Schema.Types.ObjectId, ref: 'Service', default: () => null }],

services: [{ type: Schema.Types.ObjectId, ref: 'Service', default: void 0 }],

@keyboard99 veuillez suivre les instructions dans https://github.com/Automattic/mongoose/issues/1335#issuecomment -252129243 . votre code définit une valeur par défaut pour les éléments individuels du tableau, pas pour le tableau lui-même

Il existe une solution simple à ces problèmes. Fournissez simplement la valeur par défaut comme non définie. Comme suit:

var BarSchema = new Schema({
    foos: {
        type: [FooSchema],
        default: undefined
    }
});

Travaille pour moi

Mais cela entraînera des complications avec array.push
:(

@GeneralG des complications autres que celle évidente de devoir faire doc.array = doc.array || []; avant d'appeler push() ?

Cette page vous a été utile?
0 / 5 - 0 notes