Mongoose - Can't push a subdocument into an array in parent Document - node.js

I am trying to push a subdocument(ApplicationSchema) into my Job schema. But it doesn't seem to work.
Following is my Job Schema :
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
var ApplicationSchema = require('./Application');
const Job = new Schema({
skills : {
type : Array
},
active : {
type : Boolean,
default : false
},
applications: [ApplicationSchema],
userId : {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
},{timestamps : true});
export default mongoose.model("Job", Job)
This is subdocument(ApplicationSchema). I have 5 more subdocuments in this schema.
I am pushing an object with a key-value pair of talentId and its value. But it doesn't work.
I get a new object in the array but the object I'm trying to push is not pushed.
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
var notesSchema = require('./notesSchema');
var documentSchema = require('./documentSchema');
var assessmentSchema = require('./assessmentSchema');
var interviewScheduleSchema = require('./interviewScheduleSchema');
var referenceSchema = require('./referenceSchema')
const ApplicationSchema = new Schema({
talentId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Talent'
},
applicationType: {
type: Number
}
notes: [notesSchema],
documents: [documentSchema],
assessment: [assessmentSchema],
interviewSchedule: [interviewScheduleSchema],
references: [referenceSchema]
},{
timestamps: true
});
export default ApplicationSchema;
Following is my code in the API endpoint
.post((req, res, next) => {
Job.findById(req.params.jobId)
.then((job) => {
if (job != null) {
job.applications.push(req.body);
job.save()
.then((job) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(job);
})
}
else {
err = new Error('Job ' + req.params.jobId + 'not found')
err.status = 404;
return next(err);
}
}, (err) => next(err))
.catch((err) => next(err));
})
req.body contains following object
{ talentId: '5a813e1eb936ab308c4cae51' }

If you already have the id of the job document then you can push application object direct by doing the following:
Job.update(
{ _id: req.params.jobId },
{ $push: { applications: req.body} },
callback
);
or you can use promise to handle this. and if you are only saving id of the application then you may want to change your job schema to store Id of the applications instead of whole application schema.
Please read the documentation carefully as this is very basic update query.

You have,
talentId: {type: mongoose.Schema.Types.ObjectId,
ref: 'Talent'}
But your req.body contains:
{ talentId: '5a813e1eb936ab308c4cae51' }
It should be:
{ talentId: mongoose.Types.ObjectId('5a813e1eb936ab308c4cae51') }

Turns out there was nothing wrong with code.
I was using import and export default syntax which didn't seem work well with this.
import mongoose from 'mongoose';
const Schema = mongoose.Schema;
and
export default ApplicationSchema;
I replaced them with Common JS syntax and everything worked fine.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
and
module.exports = ApplicationSchema;
I did this for Job document file and every subdocument file and the code worked.

Related

Is it possible to populate data with respect to other fields not _id in mongoose

All of the time populating data booking_unique_id is giving me null.
Here is the schema:
const chat = require('../models/chat.model')
const booking_details = new Schema({
booking_unique_id:{type:Object,ref:chat,field:'chat_screen_id'}
});
const chat_details = new Schema({
...
receiver_public_name:{type:String}
chat_screen_id:{type:Object}
});
Booking.find({booking_status:'e'}).populate('booking_unique_id'))
Currently it is not supporting in ref populate, there are issues in Mongoose Issue-3225, and Issue-1888,
For the alternative they have published populate-virtuals,
chat schema
const chat_details = new Schema({
...
receiver_public_name: { type: String }
chat_screen_id: { type: Object }
});
booking schema
const chat = require('../models/chat.model');
const booking_details = new Schema({
booking_unique_id: { type: Object }
});
booking virtual
booking_details.virtual('bookings', {
ref: chat, // The model to use
localField: 'booking_unique_id', // Find booking where `localField`
foreignField: 'chat_screen_id', // is equal to `foreignField`
// Query options, see /mongoose-query-options
// options: { sort: { name: -1 }, limit: 5 }
});
booking model
const Booking = mongoose.model('Booking', booking_details);
booking find query with populate
Booking.find({ booking_status: 'e' }).populate('bookings').exec(function(error, result) {
console.log(result);
});

Pre Update middleware is not called when updating from a bulk operation

I am trying to delete an object field from the documents whenever this field is empty and contains no sub fields.
For example if my schema is defined this way:
const Schema = mongoose.Schema;
const ObjectId = Schema.Types.ObjectId;
const mySchema = new Schema({
id: ObjectId,
//...
objAttr: {
type: ObjSchema,
default: () => undefined
},
//...
});
const myModel = mongoose.model('MyModel', mySchema);
I call the pre hook to unset the object field at each update:
mySchema.pre("updateOne", function(next){
if (this.objAttr && !this.objAttr.subField1 && !this.objAttr.subField2) {
this.objAttr = undefined;
}
next();
});
But nothing happens and the pre update is not called.
The document is updated within a bulk operation that goes like this:
const bulkOps = [];
//...
bulkOps.push({ updateOne: {
'filter': { _id: new mongoose.Types.ObjectId(someIdValue) },
'update': { $unset: { 'objAttr.subField1': 1, 'objAttr.subField2': 1 }},
}});
myModel.bulkWrite(bulkOps);
Bulk Write operations do not trigger middlewares. You may need to do individual update operations. docs

MongoDB : Missing the key _id in the item

I have been given some code to modify. It is a Node.js app using Mongoose to interact with a MongoDb instance. In Mongoose several schemas were already set up and I've added a few. Among those are these two schemas which break apart a previously existing schema (which was working fine with small data):
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var MapConvertedProjectSchema = new Schema(
{
project_id : {
type: String,
default: ""
},
dataset_id : {
type: String,
default: ""
},
properties:{
type: {},
default: {}
}
});
MapConvertedProjectSchema.pre('save', function(next) {
next();
});
mongoose.model('MapConvertedProject', MapConvertedProjectSchema);
and
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var MapConvertedLayerSchema = new Schema(
{
parent_id:
{
type: mongoose.Schema.Types.ObjectId
},
class:
{
type: String,
default: 'MapLayer',
trim: true
},
properties:
{
type: {},
default: {}
}
});
//Hook a pre save method to clean date
MapConvertedLayerSchema.pre('save', function(next) {
next();
});
mongoose.model('MapConvertedLayer', MapConvertedLayerSchema);
I use the MapConvertedLayer schema like so:
var mongoose = require('mongoose');
var LayerConverted = mongoose.model('MapConvertedLayer');
var newLayer = new LayerConverted();
//newLayer._id is automatically populated with a value
//... add other properties
newLayer.save(function(err)
{
if(err)
{
//...
}
});
This works without any issues that I can discern. However if I try similar code with MapConvertedProject I get an error:
var mongoose = require('mongoose');
var ProjectConverted = mongoose.model('MapConvertedProject');
var map_converted = new ProjectConverted();
//map_converted._id is undefined
//I tried adding the comment below to create an _id manually, but it didn't make a difference when I tried to save
//map_converted._id = mongoose.Types.ObjectId();
console.log("Project Converted ID: " + map_converted._id);
//... fill out the other properties on the schema
map_converted.save(function(err)
{
if(err)
{
//...
}
});
The save generates this error:
ValidationException: One or more parameter values were invalid: Missing the key _id in the item
Does anyone know what is causing this?
I figured this out. There was another place in the code that had a dynamoose model with the same name that was messing things up. I was able to remove all references to dynamoose since it doesn't appear to be used anymore and that cleared up this issue.

Mongoose saving for populate

I'm new to Mongoose and Nodejs developement in general and I've got a bit of confusion around how to properly set up saving my records. Here are my two schemas:
Download
var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var downloadSchema = Schema({
title : String,
description : String,
_project : { type: Schema.Types.ObjectId, ref: 'Project' }
});
module.exports = mongoose.model('Download', downloadSchema);
Project
...
var projectSchema = Schema({
name : String,
url : String,
pwd : String,
_downloads : [{type: Schema.Types.ObjectId, ref: 'Download' }]
});
module.exports = mongoose.model('Project', projectSchema);
This appears to be working correctly. The documentation explains my use-case of saving a download and linking a project, but I'm not sure how to properly populate the Project._downloads. Here's what I've done:
Express route handler:
function createDownload(req, res) {
// the Project Id is passed in the req.body as ._project
var dldata = req.body;
Project.findOne({ _id : dldata._project }, function(err, project) {
var dload = new Download(dldata);
dload.save( function (err, download) {
project._downloads.push(download._id);
project.save( function(err){
var msg = {};
if(err) {
msg.status = 'error';
msg.text = err;
}else {
msg.status = 'success';
msg.text = 'Download created successfully!';
}
res.json(msg);
});
});
});
}
This seems overcomplicated to me. Am I supposed to be manually pushing to the ._downloads array, or is that something Mongoose is supposed to handle internally based on the schema? Is there a better way to achieve it so that I can do:
Download.find().populate('_project').exec( ...
as well as:
Project.findOne({_id : _projectId}).populate('_downloads').exec( ...
According to the mongoose docs there are 2 ways to add subdocs to the parent object:
1) by using the push() method
2) by using the create() method
So I think that your code can be a bit simplified by eliminating the operation of saving a new Download item:
function createDownload(req, res) {
var dldata = req.body;
Project.findOne({ _id : dldata._project }, function(err, project) {
// handle error
project._downloads.push(dldata);
project.save(function(err) {
// handle the result
});
});
}
or
function createDownload(req, res) {
var dldata = req.body;
Project.findOne({ _id : dldata._project }, function(err, project) {
// handle error
project._downloads.create(dldata);
project.save(function(err) {
// handle the result
});
});
}

How to set ObjectId as a data type in mongoose

Using node.js, mongodb on mongoHQ and mongoose. I'm setting a schema for Categories. I would like to use the document ObjectId as my categoryId.
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Category = new Schema({
categoryId : ObjectId,
title : String,
sortIndex : String
});
I then run
var Category = mongoose.model('Schema_Category');
var category = new Category();
category.title = "Bicycles";
category.sortIndex = "3";
category.save(function(err) {
if (err) { throw err; }
console.log('saved');
mongoose.disconnect();
});
Notice that I don't provide a value for categoryId. I assumed mongoose will use the schema to generate it but the document has the usual "_id" and not "categoryId". What am I doing wrong?
Unlike traditional RBDMs, mongoDB doesn't allow you to define any random field as the primary key, the _id field MUST exist for all standard documents.
For this reason, it doesn't make sense to create a separate uuid field.
In mongoose, the ObjectId type is used not to create a new uuid, rather it is mostly used to reference other documents.
Here is an example:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Product = new Schema({
categoryId : ObjectId, // a product references a category _id with type ObjectId
title : String,
price : Number
});
As you can see, it wouldn't make much sense to populate categoryId with a ObjectId.
However, if you do want a nicely named uuid field, mongoose provides virtual properties that allow you to proxy (reference) a field.
Check it out:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var Schema_Category = new Schema({
title : String,
sortIndex : String
});
Schema_Category.virtual('categoryId').get(function() {
return this._id;
});
So now, whenever you call category.categoryId, mongoose just returns the _id instead.
You can also create a "set" method so that you can set virtual properties, check out this link
for more info
I was looking for a different answer for the question title, so maybe other people will be too.
To set type as an ObjectId (so you may reference author as the author of book, for example), you may do like:
const Book = mongoose.model('Book', {
author: {
type: mongoose.Schema.Types.ObjectId, // here you set the author ID
// from the Author colection,
// so you can reference it
required: true
},
title: {
type: String,
required: true
}
});
My solution on using ObjectId
// usermodel.js
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const ObjectId = Schema.Types.ObjectId
let UserSchema = new Schema({
username: {
type: String
},
events: [{
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}]
})
UserSchema.set('autoIndex', true)
module.exports = mongoose.model('User', UserSchema)
Using mongoose's populate method
// controller.js
const mongoose = require('mongoose')
const User = require('./usermodel.js')
let query = User.findOne({ name: "Person" })
query.exec((err, user) => {
if (err) {
console.log(err)
}
user.events = events
// user.events is now an array of events
})
The solution provided by #dex worked for me. But I want to add something else that also worked for me: Use
let UserSchema = new Schema({
username: {
type: String
},
events: [{
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}]
})
if what you want to create is an Array reference. But if what you want is an Object reference, which is what I think you might be looking for anyway, remove the brackets from the value prop, like this:
let UserSchema = new Schema({
username: {
type: String
},
events: {
type: ObjectId,
ref: 'Event' // Reference to some EventSchema
}
})
Look at the 2 snippets well. In the second case, the value prop of key events does not have brackets over the object def.
You can directly define the ObjectId
var Schema = new mongoose.Schema({
categoryId : mongoose.Schema.Types.ObjectId,
title : String,
sortIndex : String
})
Note: You need to import the mongoose module
Another possible way is to transform your _id to something you like.
Here's an example with a Page-Document that I implemented for a project:
interface PageAttrs {
label: string
// ...
}
const pageSchema = new mongoose.Schema<PageDoc>(
{
label: {
type: String,
required: true
}
// ...
},
{
toJSON: {
transform(doc, ret) {
// modify ret directly
ret.id = ret._id
delete ret._id
}
}
}
)
pageSchema.statics.build = (attrs: PageAttrs) => {
return new Page({
label: attrs.label,
// ...
})
}
const Page = mongoose.model<PageDoc, PageModel>('Page', pageSchema)
Now you can directly access the property 'id', e.g. in a unit test like so:
it('implements optimistic concurrency', async () => {
const page = Page.build({
label: 'Root Page'
// ...
})
await page.save()
const firstInstance = await Page.findById(page.id)
const secondInstance = await Page.findById(page.id)
firstInstance!.set({ label: 'Main Page' })
secondInstance!.set({ label: 'Home Page' })
await firstInstance!.save()
try {
await secondInstance!.save()
} catch (err) {
console.error('Error:', err)
return
}
throw new Error('Should not reach this point')
})

Resources