two way navigation in a mongo one to n relashionship - node.js

I'm having hard times with the mongoose relashionship system.
Here are my schemes:
const mongoose = require('mongoose');
const RecipeSchema = mongoose.Schema({
Title: { type: String },
Description: { type: String },
Complaints: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Complaint' }]
});
const Recipe = mongoose.model('Recipe', RecipeSchema);
const ComplaintSchema = mongoose.Schema({
Recipe : { type: mongoose.Schema.Types.ObjectId, ref: 'Recipe' },
Message: { type: String }
});
const Complaint = mongoose.model('Complaint', ComplaintSchema);
And here are how I'm saving my data:
var recipeEntity = new Recipe({
Title: request.body.Title,
Description: request.body.Description
});
recipeEntity.save();
var complaintEntity= new Complaint({
Message: request.body.Message.trim(),
Recipe: mongoose.Types.ObjectId(request.body.Message.RecipeId);
});
complaintEntity.save();
So far, so good... at least to me!
And now, when I try to list the recipes with the complaints, I just got an empty array of complaints:
Recipe
.find()
.populate('Complaints')
.exec(callback);
And here is the json result:
[{
"Id": "595fe6f89d63700011ee144d",
"Title": "Chocolate Cake",
"Description": "aaaa bbb cc d"
"Complaints": []
}]
So, what am I missing here?
tks for your support

I am going to assume that you are not saving both recipe and complaint during the same call. That would not make any sense: everytime you make a complaint, you wouldn't make a recipe too.
When you create a complaint, you need to save its associated recipe's ObjectId AND also add/push the complaint's ObjectId into the associated recipe's complaints.
If you are following resource naming conventions, you would have something like:
// get recipes including complaints
app.get('/recipes', function (req, res) {
Recipe.find().populate('Complaints').exec(function (err, recipes) {
console.log(recipes);
});
});
// add recipe
app.post('/recipes', function (req, res) {
var recipe = new Recipe(req.body); // simplified
recipe.save(function (err) {
if (err)
return res.send(err);
res.send('ok');
});
});
// add complaint for recipe
app.post('/recipes/:recipeID/complaints', function (req, res) {
// we query recipe bc we need it after
Recipe.findById(req.params.recipeID, function (err, recipe) {
if (err)
return res.send(err);
if (!recipe)
return res.send('No recipe found');
// add complaint
var complaint = new Complaint(req.body);
complaint.Recipe = recipe._id; // add reference in one direction
complaint.save(function (err) {
if (err)
return res.send(err);
// update recipe
recipe.Complaints.push(complaint._id); // add reference in other direction
recipe.save(function (err) {
if (err)
return res.send(err);
res.send('ok');
});
});
});
})
I think this is a good read: many to many relationship with nosql (mongodb and mongoose).

OK, how I had to save the record in the reference too, I adopted this approach:
RecipeSchema.pre('remove', function(next) {
Complaint.remove({ "Recipe" : this._id }).exec();
next();
});
ComplaintSchema.pre('remove', function(next) {
Recipe.findById(this.Recipe).exec((error, item) => {
var index = item.Complaints.indexOf(item.Complaints.find(e => e._id == this._id));
item.Complaints.splice(index, 1);
item.save(() => { next(); });
});
});
ComplaintSchema.pre('save', function(next) {
Recipe.findById(this.Recipe).exec((error, item) => {
item.Complaints.push(this);
item.save(() => { next(); });
});
});
using this trigger/event available on the mongo schemas.
That worked perfectly!

Related

How to delete mongoose schema reference?

The below controller deletes a single log perfectly well; however, I cannot for the life of me delete its reference in the Journey model.
Image of the Journey object, one that contains an array of logs
I would greatly appreciate it if someone would be able to shed some light on where i am going wrong.
Journey Model
const mongoose = require("mongoose");
const journey = mongoose.Schema({
title: {
type: String,
},
logs: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'LogEntry'
}]
},
{
timestamps: true,
})
const Journey = mongoose.model("Journey", journey);
module.exports = {Journey};
Logs Model
const mongoose = require("mongoose");
const logEntry = mongoose.Schema({
logTitle: {
type: String
},
journey: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Journey'
}],
},
{
timestamps: true,
})
const LogEntry = mongoose.model("LogEntry", logEntry);
module.exports = {LogEntry};
Controller - delete functionality
const {Journey} = require("../models/Journey");
const {LogEntry} = require("../models/Logs");
exports.log_delete_get = (req, res) => {
LogEntry.findByIdAndDelete(req.query.id).populate('journey')
.then(() => {
res.redirect("/log/index")
})
.catch(error => {
console.log(error)
})
}
EJS View - Initiates delete
<td> Delete </td>
I have tried:
I have tried to use the following, but I am not sure how to correctly implement it:
{$pull: { logs: req.query.id}})
I have tried using a for loop to iterate through the logs array and pop the specific ids but to no avail.
I am aiming to:
When I delete a log, the reference to that log is also removed from the Journey Object.
Fix
I found a solution that works, this will delete a specific Journey and remove all logs associated with that Journey.
exports.journey_delete_get = function(req, res, next){
Journey.findById(req.query.id, function(err, journey) {
if (err) {
return next(err);
}
if (!journey) {
return next(new Error('Failed to load journey ' + req.query.id));
}
journey.remove(function(err) {
if (err) {
return next(err);
}
LogEntry.find({ journey: journey._id }, function(err, logs) {
if (err) {
return next(err);
}
if (!logs) {
return next(new Error('Failed to load logs ' + journey._id));
}
logs.forEach(function(log) {
log.remove(function(err) {
if (err) {
return next(err);
}
});
});
res.redirect("/journey/index")
});
});
});
};

Mongoose: Built in validations on additional properties of SchemaType not working like min, max, minlength, maxlength etc

I am new to mongoose (MongoDB) and Nodejs, i am creating RestFul services for CRUD operations, however my issue is, for the Schema expression, the SchemaType additional properties are not being considered in the built in validation of mongoose where as it is considering required property only. Please find my model below for your reference:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let ProductSchema = new Schema({
name: {
type: String,
required: true,
minlength:2,
maxlength:10
},
price:{
type: Number,
required:true,
min:2,
max:100
},
})
//Export the model
module.exports = mongoose.model('Product', ProductSchema);
So in the above model, There are properties like min, max for Number data Type and minlength and maxLength for String data Type which are not at all considered for the validation to take place before saving into the MongoDB.
Am i missing any configuration in this? i have gone through the mongoose documentation and also through the stackoverflow's lot of posts but i did not get any information regarding this particularly.
Here is my controller as well:
const Product = require('../models/product.model');
// Simple version, without validation or sanitation
exports.test = function (req,res) {
res.send('Greetings from the Test Controller!');
};
exports.product_create = function (req,res,next) {
let product = new Product(
{
name: req.body.name,
price: req.body.price
}
);
product.save(function (err) {
if(err){
return next(err);
}
res.send('Product created Successfully');
}
)
};
exports.product_details = function (req,res, next) {
Product.findById(req.params.id, function (err, product){
if(err) {
return next(err);
}
res.send(product);
})
};
exports.product_update = function (req, res, next) {
Product.findOneAndUpdate(req.params.id, {$set: req.body},opts, function (err, product){
if(err) return next(err);
res.send('Product Updated');
})
};
exports.product_delete = function (req,res,next) {
Product.findOneAndRemove(req.params.id, function (err) {
if(err) return next(err);
res.send('Deleted product');
})
};
Please guide me if i am missing something, still that needs to incorporate into it. Thanks.

Update field within nested array using mongoose

I'm trying to update the subdocument within the array without success. The new data doesn't get saved.
Express:
router.put('/:id/:bookid', (req, res) => {
library.findOneAndUpdate(
{ "_id": req.params.id, "books._id": req.params.bookid},
{
"$set": {
"title.$": 'new title'
}
}
});
LibraryScema:
const LibarySchema = new Library({
Name: {
type: String,
required: false
},
books: [BookSchema]
});
bookScema:
const BookSchema = new Schema({
title: {
type: String,
required: false
},
Chapters: [
{
chapterTitle: {
type: String,
required: false
}
}
]
});
I only aim to update the sub-document, not parent- and sub-document at same time.
I had a similar issue. I believe there is something wrong with the $set when it comes to nested arrays (There was an entire issue thread on GitHub). This is how I solved my issue.
var p = req.params;
var b = req.body;
Account.findById(req.user._id, function (err, acc) {
if (err) {
console.log(err);
} else {
acc.websites.set(req.params._id, req.body.url); //This solved it for me
acc.save((err, webs) => {
if (err) {
console.log(err);
} else {
console.log('all good');
res.redirect('/websites');
}
});
}
});
I have a user with a nested array.
Try this code
router.put('/:id/:bookid', (req, res) => {
library.findById(
req.params.id, (err, obj) => {
if (err) console.log(err); // Debugging
obj.books.set(req.params.bookid, {
"title": 'new title',
'Chapters': 'your chapters array'
});
obj.save((err,obj)=>{
if(err) console.log(err); // Debugging
else {
console.log(obj); // See if the saved object is what expected;
res.redirect('...') // Do smth here
}
})
})
});
Let me know if it works, and I'll add explanation.
Explanation: You start by finding the right object (library in this case), then you find the correct object in the array called books.
Using .set you set the whole object to the new state. You'll need to take the data that's not changing from a previous instance of the library object.
I believe this way will overwrite and remove any data that's not passed into the .set() method. And then you save() the changed.

Object.assign() Issues on Mongoose SubDocument Schema

So when I use Object.assign, (ex. Object.assign({foo:bar1},{foo:bar2}) on a particular Mongoose Subdocument Schema, the foo:bar2 obj won't overwrite the foo:bar1 obj.
In my app I have an Account Schema with two sub-document schemas, Projects & Properties. It's in the Project sub document schema that I encounter the issue.
Here are my Models:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ProjectSchema = new Schema({
_id: String,
name: String,
}, { strict: false })
var PropertySchema = new Schema({
_id: String,
slug: String,
name: String,
type: String,
placeholder: String,
order: Number
}, { strict: false })
var AccountSchema = new Schema({
_id: String,
name: String,
slug: String,
email: String,
properties: [PropertySchema],
projects: [ProjectSchema],
}, { strict: false })
module.exports = mongoose.model('Account', AccountSchema);
Here is a snippet of my Middleware(I think that's what it's called, please correct me if I am wrong). This is where I do the Object.assign to overwrite the particular projects' properties. If it helps I can post all the routes.
router.route('/accounts/:account_id/projects/:project_id')
.get(function(req, res) {
Project.findById(req.params.project_id, function(err, project){
if (err)
res.send(err);
res.json(project);
});
})
.delete(function(req, res){
Account.findById(req.params.account_id, function(err,account){
if (err)
res.send(err);
account.projects.id(req.params.project_id).remove();
account.save(function(err) {
if(err)
res.send(err);
res.json({message: 'Project Deleted'});
});
})
})
.put(function(req, res){
Account.findById(req.params.account_id, function(err,account){
if (err)
res.send(err);
Object.assign(account.projects.id(req.params.project_id), req.body);
account.save(function(err) {
if(err)
res.send(err);
res.json({message: 'Project Updated'});
});
})
});
// ----------------------------------------------------
router.route('/accounts/:account_id/properties/:property_id')
.get(function(req, res) {
Property.findById(req.params.property_id, function(err, property){
if(err)
res.send(err);
res.json(property);
});
})
.delete(function(req, res){
Account.findById(req.params.account_id, function(err, account){
if (err)
res.send(err);
account.properties.id(req.params.property_id).remove();
account.save(function(err){
if(err)
res.send(err);
res.json({ message: 'Successfully Deleted' });
})
})
})
.put(function(req, res){
Account.findById(req.params.account_id, function(err, account) {
if (err)
res.send(err);
Object.assign(account.properties.id(req.params.property_id), req.body);
account.save(function(err) {
if(err)
res.send(err);
res.json({message: 'Property Updated'});
})
})
});
So it's the Put Method that is giving me the issue, I can Get, Post, Delete with no issues, but the Put part for Projects is where I encounter this Object.assign issue. Funny thing is that everything works perfectly for the Property Schema Put Method, and it is basically the exact same as the Project one.
If I console.log some of the values in the Project's Put Method I get the following:
console.log(account.projects.id(req.params.project_id));
//original value logs { _id: '1486609836741', foo: 'bar' }
console.log(req.body);
//updated value logs { foo: 'barred', _id: '1486609836741' }
console.log(Object.assign(account.projects.id(req.params.project_id), req.body));
//logs the original value { _id: '1486609836741', foo: 'bar' }
So when I log the updated and original values they have equal keys, shouldn't the Object.assign work? Is the original Obj more complex then what the console.log displays, and maybe the original and updated don't have equal keys?
I am kinda stumped, any help would be greatly appreciated. Let me know if I need to post more of my code. Thanks.
Object.assign(account.projects.id(req.params.project_id), req.body);
Here, you are trying to assign to an object that is result of MongooseDocumentArray.id method call on account.projects array. Which will affect just a copy of document which is returned by function, and won't affect the original array of subdocuments.
I am new to Mongoose, but I'll suggest you trying to work with account.projects as with MongooseArray:
var project = account.projects.id( req.params.project_id ),
index = account.projects.indexOf( project );
Object.assign( account.projects[ index ], req.body);

Removing references in one-to-many and many-to-many relationships in MongoDB using Mongoose and Node.js

I need to mention that I am totally aware of the fact that MongoDB is not a relational database in the first place. However it supports referencing other documents, hence some functionality should be supported, imo. Anyways, I have this relationship: a Company has many Departments and one Department belongs to one Company.
company.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var CompanySchema = new Schema({
name: {
type: String,
unique: true,
required: true
},
departments: [{
type: Schema.Types.ObjectId,
ref: 'Department'
}],
dateCreated: {
type: Date,
default: Date.now
},
dateUpdated: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Company', CompanySchema);
department.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var DepartmentSchema = new Schema({
name: {
type: String,
required: true
},
company: {
type: Schema.Types.ObjectId,
ref: 'Company'
},
dateCreated: {
type: Date,
default: Date.now
},
dateUpdated: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Department', DepartmentSchema);
Now, I am writing Node.js logic to manipulate this data using API. I get that if I create a new Department, I should add a reference to Company and I should create its reference in this Company's departments array. Simple. But what if a user changes the Company property of a Department? Say, the HR Department used to belong to Company A, but a user now moves it to Company B? We need to remove the reference to this department from Company A's array and push it to Company B. The same is when we want to delete a department. We need to find a company it belongs to and dis-associate it. My solution is working ATM, but seems rather clumsy.
routes.js
var Department = require('../../models/department'),
Company = require('../../models/company');
module.exports = function(express) {
var router = express.Router();
router.route('/')
.get(function(req, res) {
// ...
})
.post(function(req, res) {
// ...
});
router.route('/:id')
.get(function(req, res) {
// ...
})
.put(function(req, res) {
// First we need to find the department with the request parameter id
Department.findOne({ _id: req.params.id }, function(err, data) {
if (err) return res.send(err);
var department = data;
// department.name = req.body.name || department.name; Not relevant
// If the company to which the department belongs is changed
if (department.company != req.body.company._id) {
// We should find the previous company
Company.findOne({ _id: department.company }, function(err, data) {
if (err) return res.send(err);
var company = data;
// Loop through its departments
for (var i = 0; i < company.departments.length; i++) {
if (company.departments[i].equals(department._id)) {
// And splice this array to remove the outdated reference
company.departments.splice(i, 1);
break;
}
}
company.save(function(err) {
if (err) return res.send(err);
});
});
// Now we find this new company which now holds the department in question
// and add our department as a reference
Company.findOne({ _id: req.body.company._id }, function(err, data) {
if (err) return res.send(err);
var company = data;
company.departments.push(department._id);
company.save(function(err) {
if (err) return res.send(err);
});
});
}
// department.company = req.body.company._id || department.company; Not relevant
// department.dateUpdated = undefined; Not relevant
// And finally save the department
department.save(function(err) {
if (err) return res.send(err);
return res.json({ success: true, message: 'Department updated successfully.' });
});
});
})
.delete(function(req, res) {
// Since we only have id of the department being deleted, we need to find it first
Department.findOne({ _id: req.params.id}, function(err, data) {
if (err) return res.send(err);
var department = data;
// Now we know the company it belongs to and should dis-associate them
// by removing the company's reference to this department
Company.findOne({ _id: department.company }, function(err, data) {
if (err) return res.send(err);
var company = data;
// Again we loop through the company's departments array to remove the ref
for (var i = 0; i < company.departments.length; i++) {
if (company.departments[i].equals(department._id)) {
company.departments.splice(i, 1);
break;
}
}
company.save(function(err) {
if (err) return res.send(err);
});
// I guess it should be synchronously AFTER everything is done,
// since if it is done in parallel with Department.findOne(..)
// piece, the remove part can happen BEFORE the dep is found
Department.remove({ _id: req.params.id }, function(err, data) {
if (err) return res.send(err);
return res.json({ success: true, message: 'Department deleted successfully.' });
});
});
});
});
return router;
};
Is there any elegant solution to this case or it is just as it should be?
I see you have not yet captured the essence of the async nature of node.js ... for example you have a comment prior to department.save which says : and finally ... well the earlier logic may very will be still executing at that time ... also I strongly suggest you avoid your callback approach and learn how to do this using promises

Resources