How to cascade delete using Mongoose remove middleware? - node.js

I'm trying to delete all dependencies of a schema when a DELETE request is sent to my API. Deleting goes ok, but the remove middleware, which is supposed to clean the dependencies, seems like is not even getting called.
This is my Customer schema:
var mongoose = require("mongoose"),
Schema = mongoose.Schema,
passportLocalMongoose = require('passport-local-mongoose');
var Order = require('./order');
var Customer = new Schema({
name: String,
telephone: Number,
address: String,
email: String,
seller: String
});
Customer.post('remove', function(next) {
Order.remove({ customer: this._id }).exec();
next();
});
Customer.plugin(passportLocalMongoose);
module.exports = mongoose.model("Customer", Customer);
And this is my customer route:
var express = require('express');
var router = express.Router();
var passport = require('passport');
var isAuthenticated = require('./isAuthenticated');
var Customer = require('../models/customer');
var Order = require('../models/order');
// (...)
router.delete('/:customer_id', function(req, res) {
Customer.remove({ _id: req.params.customer_id }, function(err) {
if (err)
res.json({ SERVER_RESPONSE: 0, SERVER_MESSAGE: "Error deleting", ERR: err });
else res.json({ SERVER_RESPONSE: 1, SERVER_MESSAGE: "Customer deleted" });
});
});
// (...)
I did look this question and Mongoose Docs (Mongoose Middleware) but it's still unclear to me. I don't know what I'm missing or doing wrong.
Thanks in advance!
EDIT
This is my project's repository. Please, feel free to look into.

I finally found the solution to this. Middleware wasn't firing because you must use remove(), save(), etc on model instances, not the model itself.
Example:
Customer.remove({...}); won't work.
Customer.findOne({...}, function(err, customer) {
customer.remove();
});
will work and will do whatever is in Customer.post('remove').

Seems like this is the part you're focusing on:
Customer.post('remove', function(next) {
Order.remove({ customer: this._id }).exec();
next();
});
What you're doing wrong here is that the post hook is not given any flow control, so the next parameter is not actually a function but the document itself.
Change it up to this and you should get what you want:
Customer.post('remove', function(doc) {
Order.remove({ customer: doc._id }).exec();
});
From the docs:
post middleware are executed after the hooked method and all of its
pre middleware have completed. post middleware do not directly receive
flow control, e.g. no next or done callbacks are passed to it. post
hooks are a way to register traditional event listeners for these
methods.

Related

Problem with a Cast using Router.put with mongoDB in MERN app

I want to attach the router.put which will update the Boolean(isOn) in toggle button but firstly I wanted to try how it works and now I am facing the problem.
const express = require("express");
const router = express.Router();
const Buttons = require('../../models/Buttons');
// GET buttons
// This request works perfect
router.get('/', (req,res) => {
Buttons.find()
.sort({name: 1})
.then(buttons => res.json(buttons))
});
// PUT buttons
// This one doesnt work at all
router.put('/:name', function(req,res,next) {
Buttons.findByIdAndUpdate({name: req.params.name},
req.body).then(function(){
Buttons.findOne({name: req.params.name}).then(function(buttons){
res.send(buttons);
});
});
});
module.exports = router;
Model of buttons has only name: String, required: true and isOn: Boolean, required: true and data in db looks like that:
Can you tell me what did I do wrong here?
Code of Buttons modal :
const mongoose = require('mongoose');
const Schema = mongoose.Schema
const buttonSchema = new Schema ({
name: {
type: String,
required: true
},
isOn: {
type: Boolean,
required: true
}
});
module.exports = Buttons = mongoose.model("buttons", buttonSchema);
You ca only use findByIdAndUpdate when you want to update the document by matching the _id of the document
If you want to match the document by any other property (such as name in your case), you can use findOneAndUpdate
Write your query like this
router.put('/:name', function(req,res,next) {
Buttons.findOneAndUpdate({name: req.params.name},
req.body).then(function(){
Buttons.findOne({name: req.params.name}).then(function(buttons){
res.send(buttons);
});
});
});
Hope this helps
Please add your id as well which you have to update in your database
Model.findByIdAndUpdate(id, updateObj, {new: true}, function(err, model) {...
This error occur because findByIdAndUpdate need id of an Object which we want to update so it shows ObjectId error. so pass your id from front end and use it in your back-end to update particulate data.
step 1 : you can create new endpoint for update-name
router.put('/update-name', function(req,res,next) {
//here you can access req.body data comes from front-end
// id = req.body.id and name = req.body.name then use it in your
Buttons.findByIdAndUpdate(id, { name : name }, {new: true}, function(err, model) {...
}
step 2 : try this endpoint /update-name and pass your data in Body from postman

Update MongoDB with mongoose

I trying to update a field in users collection once a user is logged into the application. But the update query is not working at all.
users.js
var express = require('express');
var router = express.Router();
var User = require('../models/user');
var jwt = require('jsonwebtoken');
router.post('/login', function(req,res,next){
let promise = User.findOne({email:req.body.email}).exec();
promise.then(function(doc){
if(doc) {
if(doc.isValid(req.body.password)){
// generate token
let token = jwt.sign({username:doc.username},'secret', {expiresIn : '3h'});
setOnlineStatus(doc.username);
} else {
return res.status(501).json({message:' Invalid Credentials'});
}
} else {
return res.status(501).json({message:'User email is not registered.'})
}
});
promise.catch(function(err){
return res.status(501).json({message:'Some internal error'});
})
})
function setOnlineStatus(username){
console.log(username); // log the correct username value
User.update(
{'username': username},
{$set: {'status':'Online'}},
);
}
Model - user.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt');
var schema = new Schema({
email : {type:String, require:true},
username: {type:String, require:true},
password:{type:String, require:true},
creation_dt:{type:Date, require:true}
});
schema.methods.isValid = function(hashedpassword){
return bcrypt.compareSync(hashedpassword, this.password);
}
module.exports = mongoose.model('User',schema);
So now the problem is once a request is send to /login service, the call to setOnlineStatus() is not updating users collection with a new field status having value 'online'.
NOTE: Using another service /register users are already added to the users collection.
I'm a newbie to express and mongodb. So please help me to solve this issue.
Thank you and answers will be appreciated.
you need to define status in the schema as mongoose will ignore it while updating other wise (read: option: strict)
try adding status: {type: String} to your schema
also the update() function returns a query (read: Model.update()) it doesn't update unless you pass a callback or execute it with .exec()
User.update({'username': username}, {$set: {'status':'Online'}}).exec()

Testing mongoose pre-save hook

I am quite new to testing nodejs. So my approach might be completely wrong. I try to test a mongoose models pre-save-hook without hitting the Database. Here is my model:
// models/user.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
UserSchema = new Schema({
email: {type: String, required: true},
password: {type: String, required: true}
});
UserSchema.pre('save', function (next) {
const user = this;
user.email = user.email.toLowerCase();
// for testing purposes
console.log("Pre save hook called");
next();
});
module.exports = mongoose.model("User", UserSchema);
As I said, I do not want to hit the Database with my test, so I tried using a sinon stub of the Users save() method:
// test/models/user.js
const sinon = require("sinon");
const chai = require("chai");
const assert = chai.assert;
const User = require("../../models/user");
describe("User", function(){
it("should convert email to lower case before saving", (done) => {
const user = new User({email: "Valid#Email.com", password: "password123"});
const saveStub = sinon.stub(user, 'save').callsFake(function(cb){ cb(null,this) })
user.save((err,res) => {
if (err) return done(err);
assert.equal(res.email,"valid#email.com");
done();
})
})
});
However, If I do it like that the pre-save hook will not be called. Am I on the wrong path or am I missing something? Or is there maybe another way of triggering the pre-save hook and testing its outcome? Thanks very much in advance!
Before we start: I'm looking for the same thing as you do and I've yet to find a way to test the different hooks in Mongoose without a database. It's important that we distinguish between testing our code and testing mongoose.
Validation is middleware. Mongoose registers validation as a pre('save') hook on every schema by default. http://mongoosejs.com/docs/validation.html
Considering that validate will always be added to the model and I wish to test the automated fields in my model, I've switched from save to validate.
UserSchema = new Schema({
email: {type: String, required: true},
password: {type: String, required: true}
});
UserSchema.pre('validate', function(next) {
const user = this;
user.email = user.email.toLowerCase();
// for testing purposes
console.log("Pre validate hook called");
next();
});
The test will now look like:
it("should convert email to lower case before saving", (done) => {
const user = new User({email: "VALID#EMAIL.COM", password: "password123"});
assert.equal(res.email,"valid#email.com");
}
So What About the Pre Save Hook?
Because I've moved the business logic for automatic fields from 'save' to 'validate', I'll use 'save' for database specific operations. Logging, adding objects to other documents, and so on. And testing this only makes sense with integration with a database.
I just faced the same issue and managed to solve it by extracting the logic out of the hook, making it possible to test it in isolation. With isolation I mean without testing anything Mongoose related.
You can do so by creating a function, that enforces your logic, with the following structure:
function preSaveFunc(next, obj) {
// your logic
next();
}
You can then call it in your hook:
mySchema.pre('save', function (next) { preSaveFunc(next, this); });
This will make the reference to this available inside the function, so you can work with it.
The extracted part can then be unit tested by overwriting the next function to a function without a body.
Hope this will help anyone as it actually was a pain to solve this with my limited knowledge on Mongoose.

Node.JS Express 4 - Mongoose Does not saving data

I am trying to save a data in MongoDB with Mongoose with Express.JS 4 and Bluebird.
What I have done is like this-
bin/www
var mongoose = require('mongoose');
mongoose.Promise = require('bluebird');
.......
.......
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function()
{// successfully connected!
console.log("Successfully Connected to Mongo-DB");
});
And getting this in console-
Successfully Connected to Mongo-DB` - So, MongoDB connected successfully
models/post.js
var mongoose = require('mongoose');
var postSchema = new mongoose.Schema({
created_by: String, //should be changed to ObjectId, ref "User"
created_at: {type: Date, default: Date.now},
text: String
});
module.exports = mongoose.model('Post', postSchema);
app.js
var Post_Data = require("./models/post");
....
....
router.get('/', function(req, res, next)
{
var Post = mongoose.model("Post");
var post = new Post({
created_by: ""+Math.random()
});
console.log( Post.create(post) );
res.render(
'index',
{
title : 'Express',
site_name : 'Our Site',
layout : 'templates/layout'
}
);
});
And after that I am getting this in console-
Promise {
_bitField: 0,
_fulfillmentHandler0: undefined,
_rejectionHandler0: undefined,
_promise0: undefined,
_receiver0: undefined }
But, nothing is saved, a proof for that is -
I am finding this-
After using MongoBooster.
Update-
My DB config is like this-
"MONGO_URI": "mongodb://localhost:27017/express_test",
"MONGO_OPTIONS": {
"db": { "safe": true },
"name":"express_test"
}
So, can anyone please help, why it is not saving anything?
Thanks in advance for helping.
The .create() function is a shortcut for new Model and .save(). You are trying to .create an instance of Model rather than a simple Object. See Constructing documents in Mongoose's Models documentation for their quick example.
The return from a Mongoose data function is just the promise of an asynchronous task to be run in the future, logging that is largely pointless. Use .then() to wait until the promise has been resolved.
Error handling is missing from your code as well, something could be getting thrown there. Use a .catch() for promise error handling.
Post.create({ created_by: ""+Math.random() })
.then(function (result) {
console.log('Saved' result)
})
.catch(function (err) {
console.error('Oh No', err)
})
All of this can be done with callbacks (like the Mongoose docco examples) but promises, particularly bluebird promises are nicer.
I just use this syntax combination to create and save my model:
var myPage = new LandingPage({
user:req.user,
slug: req.body.slug,
}).save(function(err,savedModel){
if(!err){
console.log(savedModel);
}
});
You are calling the wrong model in your app.js module as you are importing the model as
var Post_Data = require("./models/post"); // <-- Post_Data model never used
....
....
but creating a new Post model instance in your router implementation as
var Post = mongoose.model("Post"); // <-- different model
var post = new Post({
created_by: ""+Math.random()
});
You need to call and use the correct models. So I would suggest you re-write your app.js module to use the save() method as:
var Post = require("./models/post"); // <-- import correct Post model
....
....
router.get('/', function(req, res, next) {
var post = new Post({ created_by: ""+Math.random() });
post.save().then(function(post) {
console.log(post); // <-- newly created post
res.render('index', {
title: 'Express',
site_name: 'Our Site',
layout: 'templates/layout'
});
})
.catch(function(err) {
console.error('Oopsy', err);
});
});
if you store post schema in a variable by require then can use that variable.
var Post_Data = require("./models/post");
so can use new Post_Data no need to use var Post = mongoose.model("Post"); because you have already exported this schema module.exports = mongoose.model('Post', postSchema);
you can try this one :
var Post_Data = require("./models/post");
router.get('/', function(req, res, next)
{
var post = new Post_Data({created_by: ""+Math.random()});
post.save(function(error, data) {
if(error) {
return res.status(500).send({error: 'Error occurred during create post'});
}
return res.render('index',{
title : 'Express',
site_name : 'Our Site',
layout : 'templates/layout'
});
});
});
So it's true that if you're creating a document in memory by calling new Post(values) that you will save it with post.save(cb); rather than 'Post.create(post);, but I'm thinking that the underlying issue (though this isn't easy to be certain of based on the code you're showing) is that you're connecting with the MongoDB driver, rather than mongoose itself. Yourdb` variable isn't shown to be declared in the code you posted, so I'm making it an assumption.
That said, if I'm right, you need to call mongoose.connect or mongoose.createConnection in order for Mongoose to know it's connected to the db and save documents to it. You can pass an existing connection to mongoose, so if you're already doing so then I apologize for my erroneous assumption.

Mongoose save callback doesn't fire

I'm new to mongoose and I'm having a hard time finding the issue within my code. I'm building a REST server using Sails.js and Mongoose. I have a node module (e.g. "sails-mongoose") for exporting mongoose, where I also connect to my database:
var mongoose = require('mongoose');
mongoose.connect('mongodb://#localhost:27017/fooria');
module.exports = mongoose;
And in my model.js:
var adapter = require('sails-mongoose');
var schema = new adapter.Schema({
firstname: {
type: String,
required: true,
trim: true
}
});
module.exports = {
schema: schema,
model: adapter.model('Collection', schema)
}
In my controller's create method I have:
create: function(req, res, next) {
var userData = {firstname: 'Test'};
var users = new Users.model(userData);
users.save(function(err, data){
if (err) return res.json(err, 400);
res.json(data, 201);
});
}
When running create method, the entry is saved to the Mongodb collection but the callback is never reached. Can someone please help me on this track, as I found similar questions but none helped me though. Thanks!
I suppose your are using Express. According Express docs you are calling res.json using incorrect parameters (wrong order).
Correct format:
res.json(code, data)
Example:
res.json(500, { error: 'message' })

Resources