Mongoose creates incomplete documents - node.js

So, I am trying to seed the database with some sample data that I have in JSON format. Here is the function:
request(options, (err, res, body) => {
if (!err && body) {
var classId;
body.forEach((element, i) => {
Student.create(element, (err, success) => {
if (err) {
console.log("This is a error", err)
} else if (i % 4 === 0) {
console.log(`This is the division for ${i}`, i % 4)
let testClass = new Class();
console.log(testClass)
testClass.name = `Class ${i}`;
testClass.students.push(success._id);
testClass.save((err, successClass) => {
console.log("This is the class created", successClass)
classId = successClass._id;
console.log("This is a the class id", classId)
User.findOne({ email: 'admin#example.com' }, (err, foundUser) => {
foundUser.classId.push(successClass._id);
})
})
} else {
console.log(classId)
Class.findById(classId, (err, foundClass) => {
foundClass.students.push(success._id);
foundClass.save();
})
}
})
})
}
});
I am facing two different problems with this code, 1. Mongoose creates a few incomplete documents. 2. ClassId variable can't store the value.
For second problem, I'll ask a different question.
The logic here is that I have a sample of 20 students, and I want to create 5 groups with 4 students each.
Here is what I get in my console
This is the division for 0 0
{ _id: 595b028dc7000c18b71d14b5,
activeStaus: true,
students: [],
dateCreated: 2017-07-04T02:50:47.296Z,
teachers: [] }
undefined
undefined
This is the division for 4 0
{ _id: 595b028dc7000c18b71d14b6,
activeStaus: true,
students: [],
dateCreated: 2017-07-04T02:50:47.296Z,
teachers: [] }
This is the division for 8 0
{ _id: 595b028dc7000c18b71d14b7,
activeStaus: true,
students: [],
dateCreated: 2017-07-04T02:50:47.296Z,
teachers: [] }
undefined
undefined
undefined
undefined
undefined
undefined
undefined
undefined
undefined
This is the division for 16 0
{ _id: 595b028dc7000c18b71d14b8,
activeStaus: true,
students: [],
dateCreated: 2017-07-04T02:50:47.296Z,
teachers: [] }
This is the division for 12 0
{ _id: 595b028dc7000c18b71d14b9,
activeStaus: true,
students: [],
dateCreated: 2017-07-04T02:50:47.296Z,
teachers: [] }
undefined
undefined
undefined
This is the class created { __v: 0,
name: 'Class 4',
_id: 595b028dc7000c18b71d14b6,
activeStaus: true,
students: [ 595b028dc7000c18b71d14a5 ],
dateCreated: 2017-07-04T02:50:47.296Z,
teachers: [] }
This is a the class id 595b028dc7000c18b71d14b6
This is the class created { __v: 0,
name: 'Class 8',
_id: 595b028dc7000c18b71d14b7,
activeStaus: true,
students: [ 595b028dc7000c18b71d14a9 ],
dateCreated: 2017-07-04T02:50:47.296Z,
teachers: [] }
This is a the class id 595b028dc7000c18b71d14b7
This is the class created { __v: 0,
name: 'Class 0',
_id: 595b028dc7000c18b71d14b5,
activeStaus: true,
students: [ 595b028dc7000c18b71d14a1 ],
dateCreated: 2017-07-04T02:50:47.296Z,
teachers: [] }
This is a the class id 595b028dc7000c18b71d14b5
As you can see in the logs that the first few documents don't have the name property and they have empty students array.
I think it has something to do the iteration, because in my head the values of i would be 0,1,2,3,4...19 in that order, even if that's not the case, I still can't understand the incomplete documents.
Edit: Here are the classSchema and studentSchema
Student Schema
var StudentSchema = new Schema({
name: String,
role: String,
added: { type: Boolean, default: false },
email: {
type: String,
lowercase: true
},
password: String,
provider: String,
pin: String,
school: String,
salt: String,
schoolId: mongoose.Schema.Types.ObjectId,
classId: mongoose.Schema.Types.ObjectId,
teacherId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
className: String,
activeStaus:{type:Boolean,default:true},
grade: String,
grade_id: Number,
teacheName: String,
teacherNumber: String,
teacherEmail: String,
courseName: String,
courseNumber: String,
courseId: Number,
period: Number,
gender: String,
number: Number,
realisticInterests: Number,
investigativeInterests: Number,
artisticInterests: Number,
socialInterests: Number,
entrepreneurialInterests: Number,
conventionalInterests: Number,
careerDecidedness: Number,
neuroticism: Number,
extraversion: Number,
opennessToExperience: Number,
conscientiousness: Number,
agreeableness: Number,
activeLearner: Number,
reflectiveLearner: Number,
theoreticalLearner: Number,
progmaticLearner: Number,
socialDesirability: Number
});
Class Schema
var classSchema = new Schema({
schoolId: mongoose.Schema.Types.ObjectId,
teachers:Array,
dateCreated:{type:Date,default:Date.now()},
name : String,
students:[{type:mongoose.Schema.Types.ObjectId,ref:'User'}],
activeStaus:{type:Boolean,default:true}
});
Sample Data
I can't post the complete data since it would overpopulate this thread, and I have checked the data thoroughly, it isn't any required property. Here is a sample
{
"Sr. No.": 1,
"school_name": "Franklin High School",
"school_number": 1001,
"school_id": "",
"grade": 11,
"grade_id": "",
"teacher_name": "Merideth Merrill",
"teacher_number": 539224,
"teacher_id": "",
"teacher_email": "",
"course_name": "Physics 1",
"course_number": 101,
"course_id": "",
"period": 1,
"student_name": "Agnes Walters",
"student_gender": "",
"student_number": "",
"student_id": 1000,
"student_pin": "",
"student_email": "",
"realistic_interests": 1.55,
"investigative_interests": 4.55,
"artistic_interests": 4.92,
"social_interests": 4.96,
"entrepreneurial_interests": 1.98,
"conventional_interests": 4.5,
"career_decidedness": 4.66,
"neuroticism": 2.49,
"extraversion": 1.98,
"openness_to_experience": 1.92,
"conscientiousness": 2.14,
"agreeableness": 0.71,
"active_learner": 4.86,
"reflective_learner": 4.08,
"theoretical_learner": 4.99,
"progmatic_learner": 3.18,
"social_desirability": 0.92
},

Related

Express.js, Mongoose: Populate returns null

I'm learning express.js and mongoDB and want to create a functionality, where user should be able add products.
My code contains 2 models, User and Product. User has a reference to Product. In User query when i try to populate Product', null is returned.
I'm testing it with postman, and the result is null in cart.
I am a beginner and don't understand how to solve the issue.
user schema
import mongoose, { Document } from 'mongoose'
import { ProductDocument } from './Products'
export type UserDocument = Document & {
firstName: string;
lastName: string;
password: string;
email: string;
isAdmin: boolean;
cart: ProductDocument[];
resetLink: string;
}
const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: true,
max: 64,
},
lastName: {
type: String,
required: true,
max: 64,
},
password: {
type: String,
required: true,
min: 6,
},
email: {
type: String,
required: true,
lowercase: true,
trim: true,
},
isAdmin: {
type: Boolean,
default: false,
},
cart: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Products',
},
],
resetLink: {
data: String,
default: '',
},
})
export default mongoose.model<UserDocument>('Users', userSchema)
product schema
import mongoose, { Document } from 'mongoose'
export type ProductDocument = Document & {
name: string;
description: string;
categories: string[];
variants: string[];
sizes: number[];
}
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true,
index: true,
},
description: {
type: String,
},
categories: [String],
variants: [String],
sizes: [Number],
})
export default mongoose.model<ProductDocument>('Products', productSchema)
and the controller for responsible for populating
export const getProduct = async (req: Request, res: Response) => {
try {
const { productId } = await req.body
const productBought = await Products.findOne({ _id: productId }).then(
(product) => {
return product
}
)
console.log(productBought)
const updated = await Users.findOneAndUpdate(
{ _id: req.params.userId },
{ $push: { cart: productBought } },
{ new: true }
)
.populate('cart')
.exec()
return res.json(updated)
} catch (error) {
return res.status(404).json({ message: 'does not work' })
}
}
output from postman
{
"isAdmin": false,
"cart": [
null,
null
],
"_id": "5f894b3c06b4a108f8d9a7ab",
"firstName": "John",
"lastName": "Doe",
"password": "$2b$08$rA0/r8iVBWeSyyerPDpPWO.ztgouQoseX0QFBZ.mlPgb6tELlrhpy",
"email": "john#gmail.com",
"__v": 0
}
your "cart" like[ObjectId,ObjectId], but your "productBought" like[{},{}].
try let cart = [];productBought.forEach((v,k) => {cart[k] = v._id});
and use { $push: { cart: { $each: cart } },

How to get the total number of occurences of a specific item in every document's array element

I am implementing favoriting/unfavoriting functionality to my express app but I have a problem on how to count the the total number the post has been favorited.
Assuming I have this Schema for Recipe
RecipeSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
maxlength: 30
},
description: {
type: String,
default: ''
},
favoritesCount: {
type: Number,
default: 0
}
})
And Schema for User
const UserSchema = new mongoose.Schema({
username: {
type: String,
minlength: 8,
required: true,
unique: true
},
fullname: {
type: String,
maxlength: 40,
minlength: 4,
required: true
},
password: {
type: String,
required: true,
minlength: 8
}
favorites: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Recipe'
}]
}, { timestamps: true });
And now assuming I have this doc of Users,
How can I count the total number the Recipe ID (5daef9a2761d4b1668214dbc) present in each User doc's favorites array?
[{
username: 'john123',
email: 'john#test.com',
favorites: ['5daef9a2761d4b1668214dbc']
}, {
username: 'jane75',
email: 'jane#test.com',
favorites: []
}, {
username: 'johnwick',
email: 'johnwick#test.com',
favorites: ['5daef9a2761d4b1668214dbc']
}]
// Should yield 2
I looked up for answers but I can't find one. I'm new to mongodb and nodejs so please bear with me. Some answers that I saw are related to Aggregation.
So far I have tried this code. But it just return the number of User documents.
const User = require('./User') // the User model
RecipeSchema.methods.updateFavoriteCount = function() {
return User.count({
favorites: {
$in: [this._id]
}
}).then((count) => {
this.favoritesCount = count;
return this.save();
});
};
You can do it with the help of aggregation and with $size. For more detail, refer to this document.
Your query
db.collection.aggregate([
{
$project: {
username: 1,
email: 1,
totalFavritesCount: {
$cond: {
if: {
$isArray: "$favorites"
},
then: {
$size: "$favorites"
},
else: "NA"
}
}
}
}
])
Result
[
{
"_id": ObjectId("5a934e000102030405000000"),
"email": "john#test.com",
"totalFavritesCount": 1,
"username": "john123"
},
{
"_id": ObjectId("5a934e000102030405000001"),
"email": "jane#test.com",
"totalFavritesCount": 0,
"username": "jane75"
},
{
"_id": ObjectId("5a934e000102030405000002"),
"email": "johnwick#test.com",
"totalFavritesCount": 1,
"username": "johnwick"
}
]
You can also check out the running code in this link.

MongoDB: A complex query with array input

I'm stuck at finding a solution for the following query.
1) a user can select many categories and subcategories.
2) the user can see all other users how are selected the same categories and subcategories within a certain radius.
Here is the Schema of the user
const userSchema = new Schema(
{
image: { type: String, default: 'NA' },
firstName: { type: String, default: 'first name' },
lastName: { type: String, default: 'last name' },
email: { type: String, lowercase: true, unique: true, trim: true },
password: { type: String, min: 6 },
gender: { type: String, emun: ['male','female','other'] },
about: { type: String, default: 'about you' },
address: {
zipCode: { type: Number, default: 000000 },
place: { type: String, default: 'place' },
street: { type: String, default: 'street' },
country: { type: String, default: 'Country' },
location: {
type: { type: String, default:'Point'},
coordinates: { type:[Number], index:'2dsphere', default:[0,0] }
}
},
interests: [
{
_id : false,
category: {
id: { type: Schema.Types.ObjectId, ref: 'Category' },
name: { type: String }
},
subCategory: [
{
_id : false,
id: { type: Schema.Types.ObjectId, ref: 'Subcategory' },
name: { type: String }
}
]
}
]
}
);
In my controller here is what I tried
homeData: async (req, res, next) => {
const limit = Number(req.params.limit);
const { latitude, longitude, minDistance, maxDistance } = getUserCurrentLocation(req);
const usersWithSameInterests = await User.aggregate([
{
"$geoNear": {
"near": {
"type": "Point",
"coordinates": [longitude, latitude]
},
"distanceField": "distance",
"minDistance": minDistance,
"maxDistance": maxDistance,
"spherical": true,
"query": { "location.type": "Point" }
}
},
{
"$match": { "interests": { "$elemMatch": {name: 'Sports'} }} // hard coded for testing
},
{ "$sort": { "distance": 1 } },
{ "$limit" : limit },
{
"$project": {
"_id": 1,
"image": 1,
"firstName":1,
"lastName":1,
"distance": 1,
"createdAt": 1
}
}
]);
return respondSuccess(res, null, {
newNotification: false,
usersWithSameInterests: usersWithSameInterests
});
},
The response i'm getting is
{
"success": true,
"message": "query was successfull",
"data": {
"newNotification": false,
"usersWithSameInterests": []
}
}
Sample categories and subcategories
Category: Sports
Subcategories: Cricket, Football, Hockey, Tennis
Category: Learning Languages
Subcategories: English, German, Spanish, Hindi
looking forward for much-needed help.
thank you.
It seems that you have a few mismatched columns.
On the $geonear pipeline, the line "query": { "location.type": "Point" } should be: 'query': {'address.location.type': 'Point'}.
And on the $match pipeline, the line { "interests": { "$elemMatch": {name: 'Sports'} } should be 'interests': { '$elemMatch:' {'category.name': 'Sports'} }
Edit:
To match multiple interests on the category and subcategory field, You can use the $in operator on the $match pipeline. Like this:
{
'interests.category.name': { $in: ['Sports'] },
'interests.subCategory.name': {$in: ['Soccer']}
}
It'll return anyone that have Sports in the category name, and Soccer on subcategory name.

In mongoose Populate ref to other field instead of _id, reference to custom field not _id

userSchema={
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
role: {
type: String
}
}
influencerSchema={
user_id: {
type: Schema.Types.ObjectId,
ref: 'users'
},
profile: {
firstname: {
type: String
},
lastname: {
type: String,
default: null
},
mob: {
type: String,
default: null
},
email: {
type: String,
default: null
},
dob: {
type: Date,
default: null
},
gender: {
type: String,
default: null
}
}
}
campaign_schema={
CampaignName: {
type: String,
required: true
},
Brandcreated: {
type: Schema.Types.ObjectId,
required: true
},
status: {
type: Number,
default: 0
},
influencers: [{
influencerId: {
type: Schema.Types.ObjectId,
ref:"influencer"
},
status: {
type: Number,
default: 0
}
}]
}
The above are 3 schema i.e User , influencer, Campaign schema.
Code use for populate is give below:
function(body) {
return new Promise(function(resolve, reject) {
campaign.find({
"_id": body.campaignId
}).populate('influencer')
.exec(function(err, doc) {
console.log(err);
if (err) {
reject();
} else {
resolve(doc);
}
});
});
}
the result given by above function is
[
{
"status": 0,
"_id": "5bc4a9bf0c67a642d74ab6d1",
"influencers": [
{
"status": 0,
"_id": "5bccc0052db612466d8f26fb",
"influencerId": "5bbef7cd8c43aa1c4e9a09b5"
}
],
"CampaignName": "testCampaign",
"Brandcreated": "5bbf7857a7a55d30426cde37",
"__v": 0
}
]
and the result expecting
[
{
"status": 0,
"_id": "5bc4a9bf0c67a642d74ab6d1",
"influencers": [
{
"status": 0,
"_id": "5bccc0052db612466d8f26fb",
"influencerId": "5bbef7cd8c43aa1c4e9a09b5"
user_id: {}
profile:{}
}
],
"CampaignName": "testCampaign",
"Brandcreated": "5bbf7857a7a55d30426cde37",
"__v": 0
}
]
Can someone told ref use influencers field in campaign schema ,i want to refer that field to user_id in influencer Schema instead of_id field i dont how to do it.
You are using the populate wrong. You actually dont want to populate influencer, but InfluencerId if I understand correctly. Instead of
.populate("influencers")
use
.populate({path: "influencers.influencerId"})
However that will not exacltly turn out the way you want to. as it will resolve to something like
"influencers" : [
"status" : 0,
"influencerId" : {
//content of influencer
}
]
If you want your the result as you stated you need to map the array afterwards.

Mongoose upsert doesn't set defaults for Schema.types.mixed properties, setDefaultsOnInsert is true

I'm using an upsert function to serialize users with my backend. I'm using Mongoose's "findOneAndUpdate" with "upsert: true" and "setDefaultsOnInsert: true" set, so that all default values defined in my Schema ought to be used for all parameters that I don't pass to the upsert.
This is my upsert:
let serialize = (params, cb) => {
User.findOneAndUpdate({"fbId" : params.fbId}, params, { upsert: true, setDefaultsOnInsert: true, new: true }, (err, doc) => {
if (err) {
//winston.log
return cb (ErrorTypes.serverError(), null);
}
else if (doc) {
return cb(null, doc);
}
else {
return cb(ErrorTypes.serverError(), null);
}
});
}
For this schema:
const userSchema = new Mongoose.Schema({
fbId: {
type: String,
index: true,
required: "Users must have a facebook ID.",
maxlength: 40
},
firstName: {
type: String,
required: "Users must have a name.",
maxlength: 40
},
lastName: {
type: String,
required: "Users must have a name.",
maxlength: 40
},
gender: {
type: String,
required: "Users must have a gender",
default: "Unknown"
},
ageRange: {
type: String,
default: "Unknown",
maxlength: 25
},
pictureUrl: {
type: String,
required: "Users must have a profile picture."
},
dateJoined: {
type: Number,
required: "Users must have a joined date.",
default: date.getTime()
},
pushId: String,
bookmarks: [{
item: {
type: Mongoose.Schema.Types.ObjectId,
ref: 'Item',
required: "An item id is required to create a bookmark."
},
lastOfferMade: Number,
timeOfferMade: Number,
}], default: [],
offersInCategory: {
type: Object,
validate: object => {
let allowedKeys = ['Furniture', 'Household', 'Electronics', 'Other'];
let correctKeys = Object.keys(object).every(key => allowedKeys.includes(key));
let min = 0;
let correctValues = Object.values(object).every(value => value >= min);
return correctKeys && correctValues;
}
}, default: {
'Furniture': 0,
'Household': 0,
'Electronics': 0,
'Other': 0
},
rejectionsInCategory: {
type: Object,
validate: object => {
let allowedKeys = ['Furniture', 'Household', 'Electronics', 'Other'];
let correctKeys = Object.keys(object).every(key => allowedKeys.includes(key));
let min = 0;
let correctValues = Object.values(object).every(value => value >= min );
return correctKeys && correctValues;
}
}, default: {
'Furniture': 0,
'Household': 0,
'Electronics': 0,
'Other': 0
},
blockedUsers: [{
userId: String,
blockedOn: Number
}], default: [],
blockedBy: [{
userId: String,
blockedOn: Number
}], default: [],
hasSeenWalkThrough: { type: Boolean, default: false },
hasBookmarkedItem: { type: Boolean, default: false },
hasSeenSearchPrompt: { type: Boolean, default: false }
});
Passing in these parameters:
var upsertNewParams = {
fbId: "newUpsertFbId",
firstName: "New upsert - User",
lastName: "Test Last Name - User",
gender: "Male",
ageRange: "18-22",
pictureUrl: "url",
pushId: "12345"
}
My upsert function returns this document:
{ _id: 5931b0efbeadf32d858a7578,
fbId: 'newUpsertFbId',
__v: 0,
firstName: 'New upsert - User',
lastName: 'Test Last Name - User',
pictureUrl: 'url',
pushId: '12345',
hasSeenSearchPrompt: false,
hasBookmarkedItem: false,
hasSeenWalkThrough: false,
blockedBy: [],
blockedUsers: [],
default: [],
bookmarks: [],
dateJoined: 1496429065565,
ageRange: '18-22',
gender: 'Male' }
I can clearly see all other defaults such as "hasSeenWalkthrough" and "blockedBy/blockedUsers/bookmarks" get set, but my "offersInCategory" and "rejectionsInCategory" for whatever reason don't, though I provide default values for those properties like any others. How do I set these default values using an upsert?

Resources