Mongoose pre save middleware of subdocument not called on second save operation - javascript

I have a mongoose schema with a subdocument. Both the parent schema and the child schema have pre save hooks. For example:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var SubSchema = new Schema( { x : Number } );
SubSchema.pre('save', function (next) {
console.log("pre save Sub");
next();
});
var MainSchema = new Schema( { x : Number, children : [SubSchema] } );
MainSchema.pre('save', function (next) {
console.log("pre save Main");
next();
});
var Main = mongoose.model('Main', MainSchema);
var m = new Main();
m.children.push( { x : 42 } );
m.save( function(err, doc) {
console.log(doc +"\n\n");
doc.children[0].x = 43;
doc.save( function(err, doc2) {
console.log(doc2 + "\n\n");
});
});
When I run this code, I get the following output:
pre save Sub
pre save Main
{ __v: 0,
_id: 50660b319aec895a50000002,
children: [ { x: 42, _id: 50660b319aec895a50000003 } ] }
pre save Main
{ __v: 0,
_id: 50660b319aec895a50000002,
children: [ { x: 43, _id: 50660b319aec895a50000003 } ] }
Any reason why the pre save hook is not running for the subdocument on the second save operation?

This is fixed in v3.2.0, by letting you do this:
doc.children.set(0, {x: 43})
doc.save()

Related

How to push data with Mongoose to a nested array in MongoDB

I'm trying to push data to a nested array in mongodb. I'm using mongoose as well.
This is just mock code to see if i can get it working:
User model:
import mongoose from "mongoose";
const CoinSchema = new mongoose.Schema({
coinID: { type: String },
});
const CoinsSchema = new mongoose.Schema({
coin: [CoinSchema],
});
const WatchlistSchema = new mongoose.Schema({
watchlistName: { type: String },
coins: [CoinsSchema],
});
const NameSchema = new mongoose.Schema({
firstName: { type: String },
lastName: { type: String },
username: { type: String },
});
const UserSchema = new mongoose.Schema({
name: [NameSchema],
watchlists: [WatchlistSchema],
test: String,
});
const User = mongoose.model("User", UserSchema);
export default User;
route:
fastify.put("/:id", async (request, reply) => {
try {
const { id } = request.params;
const newCoin = request.body;
const updatedUser = await User.findByIdAndUpdate(id, {
$push: { "watchlists[0].coins[0].coin": newCoin },
});
await updatedUser.save();
// console.dir(updatedUser, { depth: null });
reply.status(201).send(updatedUser);
} catch (error) {
reply.status(500).send("could not add to list");
}
});
request.body // "coinID": "test"
I've tried a lot of different ways to push this data but still no luck. I still get 201 status codes in my terminal which indicates something has been pushed to the DB, but when I check nothing new is there.
Whats the correct way to target nested arrays and push data to them?
It's not perfect but you could get the user document, update the user's watchlist, and then save the updated watchlist like so:
fastify.put("/:id", async (request, reply) => {
try {
const { id } = request.params;
const newCoin = request.body;
// get the user
let user = await User.findById(id);
// push the new coin to the User's watchlist
user.watchlists[0].coins[0].coin.push(newCoin);
//update the user document
const updatedUser = await User.findOneAndUpdate({ _id: id },
{
watchlists: user.watchlists,
},
{
new: true,
useFindAndModify: false
}
);
reply.status(201).send(updatedUser);
} catch (error) {
reply.status(500).send("could not add to list");
}
});

Mongoose validate length of array in schema

I want to create a schema, with arrays for names of participants at an event, I make the list of participants by doing so:
quizPart:[{
type:String,
}]
How can I validate that the length of this array is either zero (no participants at this event) or 2, and not 1 (it is a two people per team event). I want to return an error message that I can handle with ValidationError
I am adding data to this schema like so:
var school = new School();
school.quizPart=req.body.quiz;
where req.body.quiz = ["name1","name2"] or ['','']
and then, if only 1 field has a string value, I want to parse an error to the repsonse body like so:
function handleValidationError(err, body) {
for (field in err.errors) {
switch (err.errors[field].path) {
case "quizPart":
body["quizPartError"] = err.errors[field].message;
break;
}}}
This is a working example of what I mean to say.
Write a pre('update') mongoose hook and inspect the $set object if the quizParts field has length 0 or 2 or not.
index.js
const mongoose = require('mongoose');
const test = require('./test');
mongoose.connect('mongodb://localhost:27017/test2', {useNewUrlParser: true});
mongoose.set('debug',true);
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function() {
// we're connected!
});
(async() => {
try {
const testUpdate = test();
const updateQuery = {
$set: {
quizPart: [
{
type: 'Type 1'
},
{
type: 'Type 2'
}
]
}
};
const updateResult = await testUpdate.update({}, updateQuery).exec();
} catch(err) {
console.error(err);
}
})();
test.js
const mongoose = require('mongoose');
const { Schema } = mongoose;
module.exports = function() {
const testSchema = new Schema({
quizPart: [
{
type: String,
}
]
},
{
collection: 'test',
timestamps: true
});
testSchema.pre('update', function(next) {
const update = this._update.$set;
if (update.length === 0 || update.length === 2) {
return next();
}
else {
return next(new Error('Cannot have length 1!'));
}
});
return mongoose.model('test', testSchema);
};
Made the field:
quizPart:[{
type:String,
}],
And then verified the field by:
schoolSchema.path('quizPart').validate((list)=>{
alNumRegex= /^[a-z0-9]+$/i
return list[0]!=="" && list[1]!=="" || alNumRegex.test(list[0]) && alNumRegex.test(list[1]);
},'Please Register atleast two participants for quiz.');

How to implement auto increment for mongodb in strapi?

i tried adding these mongoose plugins mongoose-auto-increment and mongoose-sequence to strapi in config/functions/mongoose.js.. the counter collections are getting created.. but the sequence counts are not getting updated.. Is there a way to get these plugins working or is there a way to implement it myself?
// config/functions/mongoose.js
var autoIncrement = require('mongoose-auto-increment');
module.exports = (mongoose, connection) => {
autoIncrement.initialize(mongoose.connection);
var movieSchema = mongoose.Schema({
title: String
}, { collection : 'Tests' });
movieSchema.plugin(autoIncrement.plugin, { model: 'Test', field: 'movieId', startAt: 1 });
};
In a similar situation I solved it using a new Schema as a Counter for the Ids.
Here is the Counter Schema (models/counter.js):
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CounterSchema = Schema({
_id: {
type: String,
required: true
},
sequence: {
type: Number,
default: 0
}
}, {
collection: 'counters'
});
// export the counter model below and call this method to create the first entry in the counter's table
CounterSchema.statics.createFirstIdForMovie = async () => {
const newCounter = new counter({
_id: "movieid",
sequence: 0
});
newCounter.save();
}
And the Movie Schema would be (models/movie.js):
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const MovieSchema = new Schema({
...,
identifier: {
type: Number,
required: false,
unique: true
},
...
});
MovieSchema.pre('save', async function (next) {
// get the next value for the identifier of 'movieid'
if (this.identifier) {
// just editing, don't need to increment or set a new identifier
return;
}
let c = await counter.findById('movieid');
if (!c) {
c = await counter.createFirstIdForMovie();
}
c.sequence += 1;
await c.save();
this.identifier = c.sequence;
});
Hope it helps!
My workaround was to use a single-type as counter.
Each time I need to use and increment the counter I get the counter single-type and use the built-in createOrUpdate service to get the number.
const counters = await strapi.services.counters.find();
const updatedCounter = await strapi.services.counters.createOrUpdate({
ainumber : counters.ainumber + 1,
});
I know single-types are not ment for this, but it works and is easy to handle.
Hope helps someone

ReferenceError: next is not defined

I have Encomenda that has an array of Itens. Itens can have an array of itens.
I have the following code:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
var idvalidator = require('mongoose-id-validator');
let ItemSchema = new Schema({
produtoId:Number,
itens:[{type:Schema.Types.ObjectId, ref: 'Item'}]
});
function autoPopulateItens(next){
this.populate('itens');
next();
}
ItemSchema
.pre('findOne',autoPopulateItens)
.pre('find',autoPopulateItens);
module.exports=mongoose.model('Item',ItemSchema);
let EncomendaSchema= new Schema({
itens:[ItemSchema]
});
module.exports=mongoose.model('Encomenda',EncomendaSchema);
function log(data){
console.log(JSON.stringify(data,undefined,2))
}
And I'm trying to make a post request on postman to create an Encomenda with the following code:
{
"itens":[{
"produtoId":5,
"itens":[{
"produtoId":6,
"itens":[]
},{
"produtoId":7,
"itens":[]
},{
"produtoId":8,
"itens":[]
}]
}
]
}
But when I try to create an Encomenda and save it to the mongodb it gives me an error:
CoreMongooseArray [ { itens: [], _id: 5bd9fde20c29e35f2c0ca74a, produtoId: 5 } ]
ValidationError: Encomenda validation failed: itens.0.itens: Cast to Array failed for value "[ { produtoId: 6 },
{ produtoId: 7, itens: [] },
{ produtoId: 8, itens: [] } ]" at path "itens"
at new ValidationError (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\error\validation.js:30:11)
at model.Document.invalidate (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\document.js:2026:32)
at EmbeddedDocument.invalidate (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\types\embedded.js:289:19)
at EmbeddedDocument.$set (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\document.js:989:10)
at EmbeddedDocument._handleIndex (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\document.js:773:14)
at EmbeddedDocument.$set (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\document.js:719:22)
at EmbeddedDocument.Document (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\document.js:115:12)
at EmbeddedDocument [as constructor] (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\types\embedded.js:39:12)
at new EmbeddedDocument (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\schema\documentarray.js:75:17)
at DocumentArray.cast (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\schema\documentarray.js:334:22)
at DocumentArray.SchemaType.applySetters (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\schematype.js:763:12)
at model.$set (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\document.js:981:18)
at model._handleIndex (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\document.js:773:14)
at model.$set (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\document.js:719:22)
at model.Document (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\document.js:115:12)
at model.Model (D:\ISEP\3º\ARQSI\pr2\node_modules\mongoose\lib\model.js:90:12)
this how I try to create and save an Encomenda:
exports.encomenda_create = function (req, res,next) {
var Client = require('node-rest-client').Client;
var client = new Client();
let encomenda=new Encomenda(req.body);
encomenda.save(function (err) {
if (err) {
return next(err);
}
res.send('encomenda criada com sucesso')
})
};
Pass next as a parameter into your function like so:
exports.encomenda_create = function (req, res, next) {

How to add a javascript array variable into mongodb using node

I have defined a schema for add question and its like
var mongoose = require('mongoose');
var questionSchema = new mongoose.Schema({
question_set : String,
questions:[{
question_id : String,
question_no : Number
}]
});
I would like to insert variables say, ques_set = 'xyz' and an array question_array = [['id_1',1],['id_2',2],['id_3',3]].
I used this code to insert to mongodb
var questions = require('../schemas/questions');
exports.testing = function(req,res){
if (!req.body) return res.sendStatus(400)
var ques_set = 'xyz';
var question_array = [['id_1',1],['id_2',2],['id_3',3]];
var data = question({ques_set,question_array);
data.save(function(err) {
if (err) throw err;
else {
console.log('Question Inserted');
res.send("Question Inserted");
}
});
};
This shows me an error TypeError: object is not a function. Please help me, I just started nodejs. Thanks
You need to create a question object that matches your schema, something like this:
var Question = require('../schemas/questions');
exports.testing = function(req,res){
if (!req.body) return res.sendStatus(400)
var ques_set = 'xyz';
var question_array = [
{
question_id: "id_1",
question_no: 1
},
{
question_id: "id_2",
question_no: 2
},
{
question_id: "id_3",
question_no: 3
}
];
var data = Question({question_set: ques_set, questions: question_array});
data.save(function(err) {
if (err) throw err;
else {
console.log('Question Inserted');
res.send("Question Inserted");
}
});
};

Categories