populate nested array of nested ref - node.js

The structure of the project is
structure
The main file is index.js that is located in the main directory. This one has
'use strict'
var mongoose = require('mongoose');
var app = require('./app');
var port = process.env.port || 3000;
mongoose.connect('mongodb://localhost:27017/changeProducts',(err,res) =>{
if(err){
throw err;
}else{
console.log("Base de datos funcionando correctamente..");
app.listen(port, function(){
console.log('Servidor nodejs corriendo(app.listen)... ');
});
}
});
The version of mongoose is 5.0
The app.js is
'use strict'
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
//carga de rutas
//cr
var roleRoutes = require('./routes/cr/role');
var userRoutes = require('./routes/cr/user');
var loginRoutes = require('./routes/cr/login');
//category
var categoryRoutes = require('./routes/cr/category');
//publication
var publicationRoutes = require('./routes/publication/publication');
var offerRoutes = require('./routes/publication/offer');
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(bodyParser.json());
//configurar cabeceras
app.use((req, res, next)=>{
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'X-API-KEY, Origin, X-Requested-With','Content-Type, Accept, Access-Control-Request-Method');
res.header('Access-Control-Allow-Method', 'GET, POST, OPTIONS, PUT, DELETE');
res.header('Allow', 'GET, POST, OPTIONS, PUT, DELETE');
next();
});
//rutas base
//cr
app.use('/api', roleRoutes);
app.use('/api', userRoutes);
app.use('/api', loginRoutes);
app.use('/api', categoryRoutes);
//publication
app.use('/api', publicationRoutes);
app.use('/api', offerRoutes);
module.exports = app;
the routes like userRoutes are in the folder routes.
the first route is routes/cr/category.js
'use strict'
var express = require('express');
var CategoryController = require('../../controllers/cr/category');
var api = express.Router();
var middlewareSecurity = require('../../security/middleware');
api.get('/categories', CategoryController.getCategories );
module.exports = api;
the other one is routes/cr/user.js
'use strict'
var express = require('express');
var UserController = require('../../controllers/cr/user');
var api = express.Router();
var middlewareSecurity = require('../../security/middleware');
//api.get('/users/',middlewareSecurity.HasRole("admin,user"), UserController.getUsers );
//actions of user
api.get('/users/', UserController.getUsers);
api.get('/user/:id', UserController.getUser);
api.post('/user', UserController.saveUser);
api.put('/user/:id', UserController.updateUser);
api.delete('/user/:id', UserController.deleteUser);
module.exports = api;
The schemas are in models/cr
the first schema that is located in 'models/category.js' is
'use strict'
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var CategorySchema = Schema({
name: String,
subcategories: [{
name: String
}]
});
module.exports = mongoose.model('Category',CategorySchema);
The other Schema is UserSchema and it looks like this
'use strict'
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserSchema = Schema({
firstName: {
type: String,
required: true
},
secondName: String,
lastName: {
type: String,
required: true
},
email: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
},
status: {
type: String,
required: true
},
roles: [{
type: Schema.ObjectId,
ref: 'Role'
}],
publications: [{
title: {
type: String,
},
description: String,
status: {
type: String,
},
createdAt: {
type: Date
},
updatedAt: {
type: Date,
default: Date.now()
},
pictures: [{
name: String
}],
categories: [{
type: Schema.Types.ObjectId,
refPath: 'Category'
}],
publicationOffers: [{
idpublication: [{
type: Schema.ObjectId,
ref: 'User.publications'
}],
iduser: {
type: Schema.ObjectId,
ref: 'User'
},
createdAt: {
type: Date
}
}]
}]
});
module.exports = mongoose.model('User', UserSchema);
I have a plugin called
mongooseeder
mongooseeder
In order to start I run in terminal
node seeds.
this create some data of category.
after I run
npm start(remember to install dependencies with npm install)
the file controllers/cr/user.js has
'use strict'
var User = require('../../models/cr/user');
var bcrypt = require('bcrypt');
var path = require('path');
var fs = require('fs');
and one of the functions is
function getUsers(req, res) {
var find = User.find({}).sort('-firstName');
find.exec((err, users) => {
if (err) {
res.status(500).send({
message: "Error en la peticion"
});
return;
}
if (!users) {
res.status(404).send({
message: "no hay users"
});
return;
}
User.populate(users, {
path: 'publications.categories'
}, (err, users) => {
if (err) {
res.status(500).send({
message: "Error en la peticion"
});
return;
}
if (!users) {
res.status(404).send({
message: "User no encontrado"
});
return;
}
res.status(200).send({
users
});
// var options = {
// path: 'publications.categories',
// select: 'subcategories.name'
// };
// if (err) {
// res.status(200).send({
// users
// });
// }
// User.populate(users, options, function (err, users) {
// res.status(200).send({
// users
// });
// });
});
});
}
module.exports = {
getUser,
getUsers,
saveUser,
updateUser,
deleteUser
}
this method is used by routes/cr/user.js
this contains(look the description where is the route and what contains)
api.get('/users/', UserController.getUsers);
The link http://localhost:3000/api/users show this
{
"users": [
{
"roles": [
"5ae4a8b0a7510e3bd80917d5"
],
"publications": [],
"_id": "5ae79ee4b34bea810861ccc5",
"firstName": "santiago4",
"lastName": "torres3",
"email": "santiago5020g#hotmail.com4",
"status": "activo",
"password": "$2b$10$rONv8dlZVOMJ4kU1x4XUmuVeTiyl.B.IVrIIlPDHz.0Yuqh5w05wK",
"__v": 0
},
{
"roles": [],
"publications": [
{
"updatedAt": "2018-04-30T03:23:11.921Z",
"categories": [
"5ae4a8b0a7510e3bd80917db",
"5ae4a8b0a7510e3bd80917da"
],
"pictures": [],
"publicationOffers": [],
"_id": "5ae68cbf7cda345ec0b910f3",
"title": "publicacio2",
"description": "descripcion2",
"status": "activo",
"createdAt": "2018-04-30T03:25:51.053Z"
},
{
"updatedAt": "2018-04-30T03:45:14.159Z",
"categories": [
"5ae4a8b0a7510e3bd80917db"
],
"pictures": [],
"publicationOffers": [],
"_id": "5ae692183670e54b9c529698",
"title": "publicacio2",
"description": "descripcion2",
"status": "activo",
"createdAt": "2018-04-30T03:48:40.654Z"
},
{
"updatedAt": "2018-04-30T04:01:23.131Z",
"categories": [
"5ae4a8b0a7510e3bd80917db"
],
"pictures": [],
"publicationOffers": [],
"_id": "5ae6956d9e15516ccceb13d8",
"title": "publicacio2",
"description": "descripcion2",
"status": "activo",
"createdAt": "2018-04-30T04:02:53.177Z"
}
],
"_id": "5ae689f9a67a6284f4af4033",
"firstName": "santiago3",
"lastName": "torres3",
"email": "santiago5020g#hotmail.com3",
"status": "activo",
"password": "$2b$10$ge1lS.r/eV1nJRkQDi4dn.0AQKpJfI.a5GzBlpsN5trHefVRVjVCS",
"__v": 0
},
{
"roles": [],
"publications": [],
"_id": "5ae689f0a67a6284f4af4032",
"firstName": "santiago2",
"lastName": "torres2",
"email": "santiago5020g#hotmail.com2",
"status": "activo",
"password": "$2b$10$HND7lixmr5RT4A/Kz5gv6.it9kmHpauytIHw/UydgTOAwkbNTJf8O",
"__v": 0
},
{
"roles": [],
"publications": [],
"_id": "5ae689e5a67a6284f4af4031",
"firstName": "santiago1",
"lastName": "torres1",
"email": "santiago5020g#hotmail.com1",
"status": "activo",
"password": "$2b$10$.sNgBlSerC6f19Hd2.xnzOtpUAd8BB9JXXM5BlGIvr0dUhWOtn5IS",
"__v": 0
}
]
}
the file controllers/cr/category.js has
'use strict'
var Categories = require('../../models/cr/category');
function getCategories(req, res){
Categories.find({},(err, categories) =>{
if(err){
res.status(500).send({ message: 'Error en la peticion' });
return;
}
if(!categories){
res.status(404).send({ message: 'No hay categories' });
return
}
res.status(200).send({ categories });
});
}
module.exports = {
getCategories
}
this method is used by routes/cr/category.js
this contains(look the description where is the route and what contains)
'use strict'
var express = require('express');
var CategoryController = require('../../controllers/cr/category');
var api = express.Router();
var middlewareSecurity = require('../../security/middleware');
api.get('/categories', CategoryController.getCategories );
module.exports = api;
http://localhost:3000/api/categories will show this json
{
"categories": [
{
"subcategories": [
{
"_id": "5ae4a8b0a7510e3bd80917db",
"name": "Decoracion"
},
{
"_id": "5ae4a8b0a7510e3bd80917da",
"name": "Electrodomésticos"
},
{
"_id": "5ae4a8b0a7510e3bd80917d9",
"name": "Cocina"
},
{
"_id": "5ae4a8b0a7510e3bd80917d8",
"name": "Muebles"
}
],
"_id": "5ae4a8b0a7510e3bd80917d7",
"name": "Hogar",
"__v": 0
},
{
"subcategories": [
{
"_id": "5ae4a8b0a7510e3bd80917e0",
"name": "Computador escritorio"
},
{
"_id": "5ae4a8b0a7510e3bd80917df",
"name": "Laptop"
},
{
"_id": "5ae4a8b0a7510e3bd80917de",
"name": "Celulares"
},
{
"_id": "5ae4a8b0a7510e3bd80917dd",
"name": "Tablets"
}
],
"_id": "5ae4a8b0a7510e3bd80917dc",
"name": "Tecnología",
"__v": 0
}
]
}
I need to populate categories of UserSchema referencing subcategories of CategorySchema(See Schemas mentioned before). If you see in the file controllers/cr/user.js there is
User.populate(users, {
path: 'publications.categories'
}, (err, users) => {....
currently the UserSchema contains this
categories: [{
type: Schema.Types.ObjectId,
refPath: 'Category'
}],
I am trying to do something like this
categories: [{
type: Schema.Types.ObjectId,
refPath: 'Category.subcategories'
}],
categories is inside of publications(see schemas mentioned before)
I need to get something like this
"publications": [
{
...
"categories": [
{
"_id": "5ae4a8b0a7510e3bd80917db",
"name": "subcategory1"
},
{
"_id": "5ae4a8b0a7510e3bd80917da",
"name": "subcategory2"
}
]
},
{
....
"categories": [
{
"_id": "5ae4a8b0a7510e3bd80917e0",
"name": "subcategory1"
}
]
},
]
but it's showing me this in publications of UserSchema
"publications": [
{
...
"categories": [
"5ae4a8b0a7510e3bd80917db",
"5ae4a8b0a7510e3bd80917da"
]
},
{
...
"categories": [
"5ae4a8b0a7510e3bd80917e0"
]
},
]
publications is inisde of UserSchema(see UserSchema mentioned before)
this is the link to download the reporsitory where is the project
link of reporsitory
Please help me whith this. I have been searching and trying a lot of things whithout success. I can't find a solution in the documentation, stackoverflow and other websites. Nothing works. I tried to use dinamic references of mongoose and that does not work. I tried to use this.
https://github.com/buunguyen/mongoose-deep-populate
And I tried a lot of thing and none of them works. Please Help me!. I am a worry about it.

Related

How to update and delete item from array in mongoose?

I have a board object and I want to edit it from express and mongoose, this is my object:
"boardMembers": [
"5f636a5c0d6fa84be48cc19d",
],
"boardLists": [
{
"cards": [
{
"_id": "5f7c9b77eb751310a41319ab",
"text": "card one"
},
{
"_id": "5f7c9bb524dd8d42d469bba3",
"text": "card two"
}
],
"_id": "5f7c9b6b02c19f21a493cb7d",
"title": "list one",
"__v": 0
}
],
"_id": "5f63877177beba2e3c15d159",
"boardName": "board1",
"boardPassword": "123456",
"boardCreator": "5f636a5c0d6fa84be48cc19d",
"g_createdAt": "2020-09-17T15:57:37.616Z",
"__v": 46
}
Now this is my code, I tried to do it with $pull, but nothing happend when I check it on Postman
router.put("/delete-task/:list/:task", auth, boardAuth, async (req, res) => {
const listId = req.params.list;
const task = req.params.task;
const board = await Board.findOne({ _id: req.board._id });
if (!board) return res.status(404).send("no such board");
Board.findOneAndUpdate(
{ _id: req.board._id },
{ $pull: { "boardLists.$[outer].cards": { _id: task } } },
{
arrayFilters: [{ "outer._id": listId }],
}
);
await board.save();
res.send(board);
});
what I am missing here?
Hope this will work in your case, you just need to convert your ids into mongo objectId. So your code will look something like this:
import mongoose from "mongoose";
const task = mongoose.Types.ObjectId(req.params.task);
const listId = mongoose.Types.ObjectId(req.params.list);
board = await Board.findOneAndUpdate(
{ _id: req.board._id},
{ $pull: {
"boardLists.$[outer].cards": { _id: task }
}
},
{
arrayFilters: [{ "outer._id": listId }],
returnNewDocument: true
}
);
res.send(board);

populate embed array mongoose 5.0. Nested ref

This is the first model. It's located in the folder called models/user.js
'use strict'
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserSchema = Schema({
publications: [{
description: String,
categories: [{
type: Schema.Types.ObjectId,
ref: 'Category.subcategories'
}]
}]
The model category. It's located in the folder called models/category.js
'use strict'
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var CategorySchema = Schema({
name: String,
subcategories: [{
name: String
}]
});
Look that The UserSchema has categories. This one has this reference:
ref: 'Category.subcategories'
I have a folder called controller. The next json is from controller/category.js
'use strict'
var Categories = require('./models/category');
function getCategories(req, res){
Categories.find({},(err, categories) =>{
if(err){
res.status(500).send({ message: 'Error en la peticion' });
return;
}
if(!categories){
res.status(404).send({ message: 'No hay categories' });
return
}
res.status(200).send({ categories });
});
}
module.exports = {
getCategories
}
The json of Category looks like this.
{
"categories": [
{
"subcategories": [
{
"_id": "5ae4a8b0a7510e3bd80917db",
"name": "subcategory1"
},
{
"_id": "5ae4a8b0a7510e3bd80917da",
"name": "subcategory2"
},
{
"_id": "5ae4a8b0a7510e3bd80917d9",
"name": "subcategory3"
}
],
"_id": "5ae4a8b0a7510e3bd80917d7",
"name": "Category1",
"__v": 0
},
{
"subcategories": [
{
"_id": "5ae4a8b0a7510e3bd80917e0",
"name": "subcategory1"
},
{
"_id": "5ae4a8b0a7510e3bd80917df",
"name": "subcategory2"
}
],
"_id": "5ae4a8b0a7510e3bd80917dc",
"name": "Category2",
"__v": 0
}
]
}
Each subcategory2 belongs to one Category.
This one is in controller/user.js
The user whithout populate will looks like this
User.find({}, (err, publications) => {
if (err) {
res.status(500).send({
message: "Error en la peticion " + err
});
return;
}
if (!publications) {
res.status(404).send({
message: "Publicacion no encontrada"
});
return;
}
res.status(200).send(publications);
result
"publications": [
{
"description": "abc",
"categories": [
"5ae4a8b0a7510e3bd80917db",
"5ae4a8b0a7510e3bd80917da"
]
},
{
"description": "abcddvas asa",
"categories": [
"5ae4a8b0a7510e3bd80917e0"
]
},
]
I need to populate categories when I do this.
User.find().populate('publications.categories').then(function (err, posa) {
if (err) {
res.status(500).send(err);
return;
}
res.status(500).send(posa);
return;
});
So the final json should look like this:
"publications": [
{
"description": "abc",
"categories": [
{
"_id": "5ae4a8b0a7510e3bd80917db",
"name": "subcategory1"
},
{
"_id": "5ae4a8b0a7510e3bd80917da",
"name": "subcategory2"
}
]
},
{
"description": "abcddvas asa",
"categories": [
{
"_id": "5ae4a8b0a7510e3bd80917e0",
"name": "subcategory1"
}
]
},
]
But The result does not show me the final json.
I see that there is a option called Dynamic References.
http://mongoosejs.com/docs/populate.html
The example show this
var userSchema = new Schema({
name: String,
connections: [{
kind: String,
item: { type: ObjectId, refPath: 'connections.kind' }
}]
});
Instead of use ref, uses refPath. But for me didn't work that.
I tried to use this
https://github.com/buunguyen/mongoose-deep-populate
but that didn't work for me.
I tried to do a lot of tries. None works for me. I have been searching days and trying a lot of things but I have not been able to resolve this.

Node && Mongodb findOne is not working

Cannot read property 'title' of null !!! Showing While rendring the findone.ejs file.. But The first one in db is showing perfectly..
app.get('/books/:title', (req, res) => {
db.collection("Book")
.findOne({ 'title': req.params.title }, function(err, result) {
if (err) throw err;
res.render('findone.ejs', { Book: result});
});
})
Database Schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var BookSchema = new Schema({
title: String,
author: String,
category: String
});
module.exports = mongoose.model('Book', BookSchema);
Mongo Database.
{
"_id": {
"$oid": "5a14a5edf6fe123247b890f3"
},
"title": "ttt",
"author": "ttttt",
"category": "tttttttttttt"
}
{
"_id": {
"$oid": "5a14a5f2f6fe123247b890f4"
},
"title": "tttt",
"author": "ttttt",
"category": "ttttttttttttttttttttttttttt"
}
{
"_id": {
"$oid": "5a154e4bff45fe2c9035f9da"
},
"title": "hello ",
"author": "rabbani",
"category": "how are you"
}
If you are using mongoose then you can import the schema like this
Book = mongoose.model('Book')
and query like this
Book.findOne({"title": title}, function(err, book) {
//handle book
})
Of course you have to ensure that the title is unique.

MongoDB $geoWithin $centerSphere query

I wrote the following schema in my Node.js server:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var config = require('../../config.js');
var schema = new Schema({
title : { type: String, default: "generic"},
guide : String,
loc : {
type : {type: String, default:"Point"},
coordinates : [ ]
},
}
);
schema.index({"loc":"2dsphere"});
module.exports = mongoose.model(config.DATA_TYPE.SUPPORT, schema);
Then I wrote my dispatcher for adding, removing and research.
Adding and removing are ok but I have some problem with research.
This is my route:
router.route('/')
.post(function(req, res, next){
SupportManager.getGuides(req.body.lat, req.body.lon, function(err,data){
if(err){
return res.json({
success: false,
message: 756
});
}else{
return res.json({
success: true,
message: 856,
supports: data
});
}
});
})
where my SupportManager.getGuides is the following code:
getGuides: function(lat,lon,callback){
Support.find({ loc: { $geoWithin: { $centerSphere: [[ lon , lat], 10/3963.2]}}}, callback);
},
The strange behaviour is that I added the following object in my db:
{
"_id": "58bf2f07d5fd2a0cdde4cca9",
"guide": "a1",
"loc": {
"coordinates": [
"34",
"22"
],
"type": "Point"
},
"title": "tappp",
"__v": 0
}
But when I do the research, using lat=22 and long=34, I receive the answer with
success: true,
message: 856,
supports: []
The array "supports" is empty.
The code is perfect! Just the coordinates value are not saved as Number.
So the Schema should become:
var schema = new Schema({
title : { type: String, default: "generic"},
guide : String,
loc : {
type : {type: String, default:"Point"},
coordinates : [ { Number } ]
},
}
);
And the coordinates data in DB will be now
"loc": {
"coordinates": [
34,
22
],
"type": "Point"
},
while before the values were between the " " :
"loc": {
"coordinates": [
"34",
"22"
],
"type": "Point"
},

Mongoose: Not able to add/push a new object to an array with $addToSet or $push

I use Nodejs, Hapijs and Mongoose.
I 've a schema and model as follows.
var schema = {
name: {
type: String,
required: true
},
lectures: {}
};
var mongooseSchema = new mongoose.Schema(schema, {
collection: "Users"
});
mongoose.model("Users", mongooseSchema);
For some reason, I need to keep "lectures"
as mixed type.
While saving/creating a document I create a nested property lectures.physics.topic[] where topic is an array.
Now, I'm trying to add/push a new object to "lectures.physics.topic" using $addToSet or $push.
userModel.findByIdAndUpdateAsync(user._id, {
$addToSet: {
"lectures.physics.topic": {
"name": "Fluid Mechanics",
"day": "Monday",
"faculty": "Nancy Wagner"
}
}
});
But the document is simply not getting updated. I tried using $push too. Nothing worked. What could be the problem?
I tried to another approach using mongoclient , to update the db directly .It works please find the below code which works
db.collection("Users").update({
"_id": user._id
}, {
$addToSet: {
"lectures.physics.topic": {
"name": "Fluid Mechanics",
"day": "Monday",
"faculty": "Nancy Wagner"
}
}
}, function(err, result) {
if (err) {
console.log("Superman!");
console.log(err);
return;
}
console.log(result);
});
I have to start the mongo client every time a request is hit.This is not a feasible solution.
Mongoose loses the ability to auto detect and save changes made on Mixed types so you need to "tell" it that the value of a Mixed type has changed by calling the .markModified(path) method of the document passing the path to the Mixed type you just changed:
doc.mixed.type = 'changed';
doc.markModified('mixed.type');
doc.save() // changes to mixed.type are now persisted
In your case, you could use findById() method to make your changes by calling the addToSet() method on the topic array and then triggering the save() method to persist the changes:
userModel.findById(user._id, function (err, doc){
var item = {
"name": "Fluid Mechanics",
"day": "Monday",
"faculty": "Nancy Wagner"
};
doc.lectures.physics.topic.addToSet(item);
doc.markModified('lectures');
doc.save() // changes to lectures are now persisted
});
I'd be calling "bug" on this. Mongoose is clearly doing the wrong thing as can be evidenced in the logging as shown later. But here is a listing that calls .findOneAndUpdate() from the native driver with the same update you are trying to do:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/school');
mongoose.set('debug',true);
var userSchema = new Schema({
name: {
type: String,
required: true
},
lectures: { type: Schema.Types.Mixed }
});
var User = mongoose.model( "User", userSchema );
function logger(data) {
return JSON.stringify(data, undefined, 2);
}
async.waterfall(
[
function(callback) {
User.remove({},function(err) {
callback(err);
});
},
function(callback) {
console.log("here");
var user = new User({ "name": "bob" });
user.save(function(err,user) {
callback(err,user);
});
},
function(user,callback) {
console.log("Saved: %s", logger(user));
User.collection.findOneAndUpdate(
{ "_id": user._id },
{
"$addToSet": {
"lectures.physics.topic": {
"name": "Fluid Mechanics",
"day": "Monday",
"faculty": "Nancy Wagner"
}
}
},
{ "returnOriginal": false },
function(err,user) {
callback(err,user);
}
);
}
],
function(err,user) {
if (err) throw err;
console.log("Modified: %s", logger(user));
mongoose.disconnect();
}
);
This works perfectly with the result:
Saved: {
"__v": 0,
"name": "bob",
"_id": "55cda1f5b5ee8b870e2f53bd"
}
Modified: {
"lastErrorObject": {
"updatedExisting": true,
"n": 1
},
"value": {
"_id": "55cda1f5b5ee8b870e2f53bd",
"name": "bob",
"__v": 0,
"lectures": {
"physics": {
"topic": [
{
"name": "Fluid Mechanics",
"day": "Monday",
"faculty": "Nancy Wagner"
}
]
}
}
},
"ok": 1
}
You neeed to be careful here as native driver methods are not aware of the connection status like the mongoose methods are. So you need to be sure a connection has been made by a "mongoose" method firing earlier, or wrap your app in a connection event like so:
mongoose.connection.on("connect",function(err) {
// start app in here
});
As for the "bug", look at the logging output from this listing:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/school');
mongoose.set('debug',true);
var userSchema = new Schema({
name: {
type: String,
required: true
},
lectures: { type: Schema.Types.Mixed }
});
var User = mongoose.model( "User", userSchema );
function logger(data) {
return JSON.stringify(data, undefined, 2);
}
async.waterfall(
[
function(callback) {
User.remove({},function(err) {
callback(err);
});
},
function(callback) {
console.log("here");
var user = new User({ "name": "bob" });
user.save(function(err,user) {
callback(err,user);
});
},
function(user,callback) {
console.log("Saved: %s", logger(user));
User.findByIdAndUpdate(
user._id,
{
"$addToSet": {
"lectures.physics.topic": {
"name": "Fluid Mechanics",
"day": "Monday",
"faculty": "Nancy Wagner"
}
}
},
{ "new": true },
function(err,user) {
callback(err,user);
}
);
}
],
function(err,user) {
if (err) throw err;
console.log("Modified: %s", logger(user));
mongoose.disconnect();
}
);
And the logged output with mongoose logging:
Mongoose: users.remove({}) {}
here
Mongoose: users.insert({ name: 'bob', _id: ObjectId("55cda2d2462283c90ea3f1ad"), __v: 0 })
Saved: {
"__v": 0,
"name": "bob",
"_id": "55cda2d2462283c90ea3f1ad"
}
Mongoose: users.findOne({ _id: ObjectId("55cda2d2462283c90ea3f1ad") }) { new: true, fields: undefined }
Modified: {
"_id": "55cda2d2462283c90ea3f1ad",
"name": "bob",
"__v": 0
}
So in true "What the Fudge?" style, there is a call there to .findOne()? Which is not what was asked. Moreover, nothing is altered in the database of course because the wrong call is made. So even the { "new": true } here is redundant.
This happens at all levels with "Mixed" schema types.
Personally I would not nest within "Objects" like this, and just make your "Object keys" part of the standard array as additional properties. Both MongoDB and mongoose are much happier with this, and it is much easier to query for information with such a structure.
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/school');
mongoose.set('debug',true);
var lectureSchema = new Schema({
"subject": String,
"topic": String,
"day": String,
"faculty": String
});
var userSchema = new Schema({
name: {
type: String,
required: true
},
lectures: [lectureSchema]
});
var User = mongoose.model( "User", userSchema );
function logger(data) {
return JSON.stringify(data, undefined, 2);
}
async.waterfall(
[
function(callback) {
User.remove({},function(err) {
callback(err);
});
},
function(callback) {
console.log("here");
var user = new User({ "name": "bob" });
user.save(function(err,user) {
callback(err,user);
});
},
function(user,callback) {
console.log("Saved: %s", logger(user));
User.findByIdAndUpdate(
user._id,
{
"$addToSet": {
"lectures": {
"subject": "physics",
"topic": "Fluid Mechanics",
"day": "Monday",
"faculty": "Nancy Wagner"
}
}
},
{ "new": true },
function(err,user) {
callback(err,user);
}
);
}
],
function(err,user) {
if (err) throw err;
console.log("Modified: %s", logger(user));
mongoose.disconnect();
}
);
Output:
Mongoose: users.remove({}) {}
here
Mongoose: users.insert({ name: 'bob', _id: ObjectId("55cda4dc40f2a8fb0e5cdf8b"), lectures: [], __v: 0 })
Saved: {
"__v": 0,
"name": "bob",
"_id": "55cda4dc40f2a8fb0e5cdf8b",
"lectures": []
}
Mongoose: users.findAndModify({ _id: ObjectId("55cda4dc40f2a8fb0e5cdf8b") }) [] { '$addToSet': { lectures: { faculty: 'Nancy Wagner', day: 'Monday', topic: 'Fluid Mechanics', subject: 'physics', _id: ObjectId("55cda4dc40f2a8fb0e5cdf8c") } } } { new: true, upsert: false, remove: false }
Modified: {
"_id": "55cda4dc40f2a8fb0e5cdf8b",
"name": "bob",
"__v": 0,
"lectures": [
{
"faculty": "Nancy Wagner",
"day": "Monday",
"topic": "Fluid Mechanics",
"subject": "physics",
"_id": "55cda4dc40f2a8fb0e5cdf8c"
}
]
}
So that works fine, and you don't need to dig to the native methods just to make it work.
Properties of an array make this much easy to query and filter, as well as "aggregate" information across the data, which for all of those MongoDB likes a "strict path" to reference all information. Otherwise you are diffing to only "specific keys", and those cannot be indexed or really searched without mentioning every possible "key combination".
Properties like this are a better way to go. And no bugs here.

Resources