Mongoose Pre Save hook on Parent Object not executing - node.js

I am new to mongoose as a method for updating Mongo. I am using nmap to map my network and provide some visibility on servers and ports that are open (as part of a largeer strategy). This portion of the strategy alos pulls information from CHEF and vSphere which is all linked together in a GUI. Those portions are just simple single level objects and is working fine, however the NMAP portion has a parent/child object model. The object model has a Server object and a Port object - both of which have addedOn and updatedOn dates.
Unfortunately mongoose hook for the pre save is only firing for the children objects (each of them) and not for the parent object - although the parent object is getting saved. I really want the parent object to also have the addedOn and updatedOn dates. I can't seem to figure it out. Thanks in advance
A disclaimer, the code below is snipped out of a larger application written in folktale, ramda and point free form.
NodeJs 5.1, Mongoose 4.4.1
The port.js File
const mongoose = require('mongoose');
const Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
const PortSchema = new Schema({
id : ObjectId,
port : String,
protocol : String,
application : String,
addedOn : { type: Date, default: Date.now, setDefaultsOnInsert: true },
updatedOn : { type: Date, default: Date.now }
});
PortSchema.pre('save', function(next){
this.update({},{
$set: {
updatedOn: new Date()
}
})
next();
});
module.exports.model = mongoose.model('Port', PortSchema)
module.exports.schema = PortSchema
The server.js file
const mongoose = require('mongoose');
const PortSchema = require('./port').schema
const Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
const ServerSchema = new Schema({
id : ObjectId,
address : String,
addressType : String,
ports : [PortSchema],
addedOn : { type: Date, default: Date.now, setDefaultsOnInsert: true },
updatedOn : { type: Date, default: Date.now }
});
ServerSchema.pre('save', function(next){
this.update({},{
$set: {
updatedOn: new Date()
}
})
next();
});
module.exports.model = mongoose.model('Server', ServerSchema)
module.exports.schema = ServerSchema
Upsert code in my application
// nmapUpsert :: (criteria, {}) => Task {ok :: int, ...}
const nmapUpsert = adapt.findOneAndUpdate(Server)
// persist :: [{address :: string, ...}] => [Task {ok :: int, ...}]
const persistNmap = R.map((data) => {
return nmapUpsert({ "address": data.address }, data)
})
Here is my model -> task upsert adapter (adapt.findOneAndUpdate)
module.exports.findOneAndUpdate = (originalModel) => {
return (criteria, record) => {
return new Task((reject, resolve) => {
const callback = (error, updatedModel) => {
if (error) {
reject(error)
}
else {
if(!updatedModel) {
resolve(null)
}else {
// this looks to be required to apply defaults from the Schema
updatedModel.save((error) => {
if (error) {
reject(error)
}
resolve(updatedModel)
})
}
}
}
originalModel.findOneAndUpdate(criteria, record, {upsert: true}, callback)
})
}
}
Admittedly that last function is a bit cludgy - but I am just trying to figure this out before I clean it up.

If I change the hooks to findOneAndUpdate I get better behavior. The server objectgets the addedOn on insert (and updatedOn as well), then subsequently just the updatedOn gets updated. Unforutnately the entire Ports object collection gets replaced - even on update - and the addedOn and updatedOn both get updated at that point.
The port.js File
const mongoose = require('mongoose');
const Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
const PortSchema = new Schema({
id : ObjectId,
port : String,
protocol : String,
application : String,
addedOn : { type: Date, default: Date.now, setDefaultsOnInsert: true },
updatedOn : { type: Date, default: Date.now }
});
PortSchema.pre('findOneAndUpdate', function(next){
this.update({},{
$set: {
updatedOn: new Date()
}
})
next();
});
module.exports.model = mongoose.model('Port', PortSchema)
module.exports.schema = PortSchema
The server.js file
const mongoose = require('mongoose');
const PortSchema = require('./port').schema
const Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
const ServerSchema = new Schema({
id : ObjectId,
address : String,
addressType : String,
ports : [PortSchema],
addedOn : { type: Date, default: Date.now, setDefaultsOnInsert: true },
updatedOn : { type: Date, default: Date.now }
});
ServerSchema.pre('findOneAndUpdate', function(next){
this.update({},{
$set: {
updatedOn: new Date()
}
})
next();
});
module.exports.model = mongoose.model('Server', ServerSchema)
module.exports.schema = ServerSchema

Related

When making a POST request for a subdocument it comes back as undefined? mongoose, express

I'm trying a to make a post request to save new data to one of my subdocuments, but I'm getting an error when trying to access the subdocument in the function. It keeps coming back as undefined. How can I get a specific user by id and create and add new data the one it's subdocuments?
model
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ClassworkSchema = new Schema({
name: String,
time: Date,
todo: String,
isDone: false
});
const OutcomesSchema = new Schema({
name: String,
time: Date,
todo: String,
isDone: false,
isApproved: false
})
const MeetupSchema = new Schema({
name: String,
time: Date,
location: String,
attended: false
})
const UserSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
classwork:{type: [ClassworkSchema], default: []},
outcomes: [OutcomesSchema],
meetups: [MeetupSchema],
});
module.exports = User = mongoose.model('users', UserSchema);
controller
classworkRouter.post("/:userId/", (req, res) => {
User.findById(req.params.user_id, (err, user) => {
if (err) return err;
new_classwork = new classwork();
(new_classwork.name = req.body.name),
(new_classwork.date = req.body.date),
(new_classwork.todo = req.body.todo),
(new_classwork.isDone = req.body.isDone);
console.log(new_classwork);
user.classwork = {};
user.classwork.name = req.body.classwork.name;
user.classwork.todo = user.classwork.todo;
if (user.classwork === undefined) {
user.classwork.push(new_classwork);
} else {
user.classwork = [new_classwork];
}
user.save(function (err, data) {
if (err) res.send(err);
res.json({ message: "work added", data: data });
});
});
});
you can see the error in the terminal in the following phto:
in this part of code
new_classwork = new classwork()
you shoud defined the new_classwrok like this :
let new_classwork = new classwork()
and new classwork() is not defined, you must to require Model of classwork in controller..
in schema file export schemas like this :
const User = mongoose.model('users', UserSchema);
const Classwork = mongoose.model('Classwork', ClassworkSchema );
module.exports = {
User : User ,
Classwork : Classwork
}
in controller.js
const {User} = require('../models/certification');
const {Classwork } = require('../models/certification');
after require models you can use new Crosswork like this :
note: Classwork with uppercase character
let new_classwork = new Classwork()

Nodejs Mongodb update multiple collections

In mongodb have two collections for now Event and Packages. So basically the model looks like this
Event Model
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const Schema = mongoose.Schema;
const bcrypt = require('bcrypt-nodejs');
const EventSchema = new Schema({
_id: mongoose.Schema.Types.ObjectId,
eventname: {
type: String,
required : true,
},
eventdesc: {
type: String,
required : true,
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Event', EventSchema);
Package Model looks like this
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const Schema = mongoose.Schema;
const bcrypt = require('bcrypt-nodejs');
const PackageSchema = new Schema({
_id: mongoose.Schema.Types.ObjectId,
eventpackages: {
type: Object,
required : false,
},
event: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Event'
},
createdAt: {
type: Date,
default: Date.now
},
updatedAt: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Package', PackageSchema);
Here I am trying to update an event so when the event will be updated the packages will be updated also. So in nodejs I have done like this
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
/* UPDATE EVENT */
router.put('/:id', function(req, res, next) {
upload(req, res, function(err) {
let EventData = {
eventname : req.body.eventname,
eventdesc : req.body.eventdesc,
}
let EventPackages = {
eventpackages : req.body.sections,
}
Event.findByIdAndUpdate(req.params.id, EventData, function (err, post) {
if (err) return next(err);
Package.findAndModify({
query: { event: req.params.id },
update: { $inc: { EventPackages } },
new: true,
upsert: true
})
res.json(post);
});
});
});
module.exports = router;
But its not working at all. If I will deleted the codes for package update it will work for events.
So can someone tell me how to make this work so that I can update multiple collections at a time.
you can try this,
var query = {
'event': req.params.id
};
var dataToUpdate = {
$set: {
///
}
};
Package.updateMany(query, datatoUpdate, function(err, result) {
if (err) return handleError(err);
return Promise.resolve(result);
});

Mongoose populate empty collection with other collections

I am trying to create a method for populating collection with a pre-defined Schema static method. I want it to grab all documents from 2 different collections and return them. It seems logical to me to create a separate Schema and return this.find().populate() callback as a result, but I can't get it to work...
Here are my Schemas:
dht.model.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
// Dht Schema from MongoDB
var DhtSchema = mongoose.Schema({
location: {
type: String,
required: true
},
reading: {
type: String,
required: true
}
}, {
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
},
});
co2.model.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
// CO2 Schema from MongoDB
var CO2Schema = mongoose.Schema({
location: {
type: String,
required: true
},
reading: {
type: String,
required: true
}
}, {
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
},
});
excel.model.js
This is the schema I want to populate dynamically, since new data is added to the database from MQTT middleware every 20 minutes and it has to return old and new results every time it has been invoked.
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
// Dht Schema from MongoDB
var ExcelSchema = mongoose.Schema({
co2: [{
type: Schema.ObjectId,
ref: 'co2'
}],
dht: [{
type: Schema.ObjectId,
ref: 'dhts'
}]
});
ExcelSchema.statics.showAll = function (cb) {
return this.find().populate('co2 dht').exec(function (err, cb) {
if (err) {
return err;
}
return cb;
});
};
module.exports = mongoose.model('merged_sensors', ExcelSchema);
When method is invoked within the API, it freezes and returns 504. My only assumption is, that it can't access the collections for some reason, but I am, most likely, wrong.
Update:
Here is a controller, which makes an API call for that Collection:
excel.controller.js
var Xls = require('./../models/excel.model'),
express = require("express"),
router = express.Router();
router.get("/all", function (req, res) {
Xls.showAll(function (err, results) {
if (err) throw err;
res.json(results);
})
});
module.exports = router;

mongoose findOneAndSave and pre hook, and setting an initial value (addedOn date)

I am new to mongoose and honestly have a ton of reading to do still, but I am working on a small pet project that reads and writes to a mongo db. In this instance I would like to use mongoose to assure a consistent document model.
The problem is assuring default valuesin both an insert and update operations independently. In particualr I am referring to addedOn and updatedOn field respectively. The method I am calling for persistence is the findOneAndUpdate which now has support for middleware hooks (as of 4.0).
I was able to get the basic hook to work (applying the updatedOn date every time), but have been unable to figure out how to add suport (the mongoose way) for the insert case where the addedOn is defaulted. This is the simple schema and hook (attmpeting to handle insert and update):
const TestSchema = new Schema({
id : ObjectId,
name : String,
addedOn : { type: Date, default: Date.now },
updatedOn : { type: Date, default: Date.now }
});
TestSchema.pre('findOneAndUpdate', function(next) {
console.log('hook::findOneAndUpdate')
// update defaults
this.update({addedOn: {$exists:true}},{
$set: {
updatedOn: new Date()
}
})
// insert defaults
this.update({addedOn: {$exists:false}},{
$set: {
addedOn: new Date(),
updatedOn: new Date()
}
})
next();
});
No surprise (because it is really swag) it is not working. THis inserts a row every time the app is run. It appears if adding a criteria to the update method overwrites the default match (haven't dug into the source).
This is my initial snippet for the solely update case and it finds the row and updates correctly, but as mentioned cannot handle the addedOn (it would update it everytime)
TestSchema.pre('findOneAndUpdate', function(next) {
console.log('hook::findOneAndUpdate')
this.update({},{
$set: {
updatedOn: new Date()
}
})
next();
});
Driving all of this is this snippet (to keep the code as close to the idiom of the folktale implmentation of the application I am working on)
mongoose.connect('mongodb://localhost/network');
const model = mongoose.model('TestSchema', TestSchema)
const schema = TestSchema
const collection = model
const findOneAndUpdate = (collection) => {
return (criteria, record) => {
return new Task((reject, resolve) => {
const callback = (error, data) => {
if (error) {
reject(error)
}
else {
resolve(data)
}
}
collection.findOneAndUpdate(criteria, record, {upsert: true}, callback)
})
}
}
const upsert = findOneAndUpdate(collection)
upsert({name: "me"}, {name: "me"}).fork(console.log, console.error)
I could modify my findOneAndUpdate function to add the addedOn - but I would really like to use the mongoose hooks properly.
Well here is one answer, although it requires a second "save" after the findOne and update:
const mongoose = require('mongoose')
const Schema = mongoose.Schema
, ObjectId = Schema.ObjectId
, Task = require('data.task') ;
const TestSchema = new Schema({
id : ObjectId,
name : String,
genre: {type: String, default: 'Action'},
addedOn : { type: Date, default: Date.now, setDefaultsOnInsert: true },
updatedOn : { type: Date, default: Date.now }
});
TestSchema.pre('findOneAndUpdate', function(next) {
console.log('hook::findOneAndUpdate')
this.update({},{
$set: {
updatedOn: new Date()
}
})
next();
});
mongoose.connect('mongodb://localhost/network');
const model = mongoose.model('TestSchema', TestSchema)
const findOneAndUpdate = (originalModel) => {
return (criteria, record) => {
return new Task((reject, resolve) => {
const callback = (error, updatedModel) => {
if (error) {
reject(error)
}
else {
if(!updatedModel) {
resolve(null)
}else {
// this looks to be required to apply defaults from the Schema
updatedModel.save((error) => {
if (error) {
reject(error)
}
resolve(updatedModel)
})
}
}
}
originalModel.findOneAndUpdate(criteria, record, {upsert: true}, callback)
})
}
}
const upsert = findOneAndUpdate(model)
upsert({name: "me"}, {name: "me"}).fork(console.log, console.error)
I don't love it... BUT it works.

Mongoose one-to-many

can you explain me how to organize mongoose models to create one to many connections? It is needed keep separate collections.
suppose i have stores and items
//store.js
var mongoose = require('mongoose');
module.exports = mongoose.model('Store', {
name : String,
itemsinstore: [ String]
});
//item.js
var mongoose = require('mongoose');
module.exports = mongoose.model('Item', {
name : String,
storeforitem: [String]
});
Am i doing it in the right way?
And how to access pass data to arryas?
Here is the code yo enter name to item. But how to enter id to array of id's (itemsinstore)?
app.post('/api/stores', function(req, res) {
Store.create({
name: req.body.name,
}, function(err, store) {
if (err)
res.send(err);
});
})
You should use model reference and populate() method:
http://mongoosejs.com/docs/populate.html
Define your models:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var storeSchema = Schema({
name : String,
itemsInStore: [{ type: Schema.Types.ObjectId, ref: 'Item' }]
});
var Store = mongoose.model('Store', storeSchema);
var itemSchema = Schema({
name : String,
storeForItem: [{ type: Schema.Types.ObjectId, ref: 'Store' }]
});
var Item = mongoose.model('Item', itemSchema);
Save a new item into an existing store:
var item = new Item({name: 'Foo'});
item.save(function(err) {
store.itemsInStore.push(item);
store.save(function(err) {
// todo
});
});
Get items from a store
Store
.find({}) // all
.populate('itemsInStore')
.exec(function (err, stores) {
if (err) return handleError(err);
// Stores with items
});
You can do using the best practices with Virtuals.
Store.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const StoreSchema = new Schema({
name: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
}
})
StoreSchema.virtual('items', {
ref: 'Item',
localField: '_id',
foreignField: 'storeId',
justOne: false // set true for one-to-one relationship
})
module.exports = mongoose.model('Store', StoreSchema)
Item.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const ItemSchema = new Schema({
storeId: {
type: Schema.Types.ObjectId,
required: true
},
name: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model('Item', ItemSchema)
StoreController.js
const Store = require('Store.js')
module.exports.getStore = (req, res) => {
const query = Store.findById(req.params.id).populate('items')
query.exec((err, store) => {
return res.status(200).json({ store, items: store.items })
})
}
Keep in mind that virtuals are not included in toJSON() output by default. If you want populate virtuals to show up when using functions that rely on JSON.stringify(), like Express' res.json() function, set the virtuals: true option on your schema's toJSON options.
// Set `virtuals: true` so `res.json()` works
const StoreSchema = new Schema({
name: String
}, { toJSON: { virtuals: true } });
Okay, this is how you define a dependancy:
var mongoose = require('mongoose');
module.exports = mongoose.model('Todo', {
name : String,
itemsinstore: [{ type: Schema.Types.ObjectId, ref: 'Item' }]
});
And make sure you have different names:
var mongoose = require('mongoose');
module.exports = mongoose.model('Item', {
name : String,
storeforitem: [String]
});
Keep an eye on Item in both cases.
And then you just want to pass the array of ObjectIDs in it. See more here: http://mongoosejs.com/docs/populate.html
Try this:
Store.findOne({_id:'5892b603986f7a419c1add07'})
.exec (function(err, store){
if(err) return res.send(err);
var item = new Item({name: 'Foo'});
item.save(function(err) {
store.itemsInStore.push(item);
store.save(function(err) {
// todo
});
});

Resources