Related
Below code snippets contain 2 schemas. I am trying to fetch all club items that does NOT have a booking with a given date and time in the booking schema. As you can see the booking schema has a clubItem property where it takes an object. I want to get the club items that is not booked in the given date and time.
For example if there is a booked room from 21-11-2022, 9-10, I want all available club items to be fetched except that booked club item. The last code snippet represents my approach for tackling this issues but I can't quite get what I want. I tried to aggregation and tried to understand its concept but I am having issues implementing my condition.
const { default: mongoose } = require("mongoose");
const { PaymentStatusStrings, BookingStatusStrings, PaymentMethodStrings } = require("../utils/enums");
const { ClubItem } = require("./club_item");
const bookingSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
club: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Club',
required: true
},
clubItems: {
type: ClubItem.schema,
required: true
},
date: {
type: String,
required: true
},
startingTime: {
type: String,
required: true
},
endingTime: {
type: String,
required: true
},
totalHours: {
type: Number,
required: true
},
extraTime: {
type: Number,
required: true,
default: 0
},
subtotal: {
type: Number,
required: true
},
tax: {
type: Number,
required: true
},
totalPrice: {
type: Number,
required: true
},
paymentStatus: {
type: String,
enum: Object.values(PaymentStatusStrings),
required: true
},
status: {
type: String,
enum: Object.values(BookingStatusStrings),
required: true
},
paymentMethod: {
type: String,
enum: Object.values(PaymentMethodStrings),
required: true
},
DateTimeStamp:
{
type: Date,
default: Date.now
}
})
bookingSchema.virtual('id').get(function () {
return this._id.toHexString();
});
bookingSchema.set('toJSON', {
virtual: true,
});
exports.Booking = mongoose.model('Booking', bookingSchema);
const { default: mongoose } = require("mongoose");
const { CategoryStrings, ClubItemStatusStrings } = require("../utils/enums");
const clubItemSchema = mongoose.Schema({
name: {
type: String,
required: true
},
pricePerHour: {
type: Number,
required: true
},
minCapacity: {
type: Number,
required: true
},
maxCapacity: {
type: Number,
required: true
},
multiplayer: Boolean,
description: String,
image1: {
type: String,
required: true
},
image2: String,
image3: String,
category: {
type: String,
enum: Object.values(CategoryStrings),
required: true
},
club: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Club',
required: true
},
status: {
type: String,
enum: Object.values(ClubItemStatusStrings),
required: true
}
})
clubItemSchema.virtual('id').get(function () {
return this._id.toHexString();
});
clubItemSchema.set('toJSON', {
virtual: true,
});
exports.ClubItem = mongoose.model('ClubItem', clubItemSchema);
const { default: mongoose } = require("mongoose");
const { CategoryStrings, ClubItemStatusStrings } = require("../utils/enums");
const clubItemSchema = mongoose.Schema({
name: {
type: String,
required: true
},
pricePerHour: {
type: Number,
required: true
},
minCapacity: {
type: Number,
required: true
},
maxCapacity: {
type: Number,
required: true
},
multiplayer: Boolean,
description: String,
image1: {
type: String,
required: true
},
image2: String,
image3: String,
category: {
type: String,
enum: Object.values(CategoryStrings),
required: true
},
club: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Club',
required: true
},
status: {
type: String,
enum: Object.values(ClubItemStatusStrings),
required: true
}
})
clubItemSchema.virtual('id').get(function () {
return this._id.toHexString();
});
clubItemSchema.set('toJSON', {
virtual: true,
});
exports.ClubItem = mongoose.model('ClubItem', clubItemSchema);
const express = require('express');
const { Booking } = require('../../models/booking');
const { ClubItem } = require('../../models/club_item');
const { Club } = require('../../models/club');
const { User } = require('../../models/user');
const router = express.Router();
router.get('/getPlaystationRooms/:datetime', async (req, res) => {
const bookingDateTime = req.params.datetime
const clubItems = await ClubItem.aggregate([
{
$lookup: {
from: "booking",
localField: "_id",
foreignField: "clubItems._id",
as: "freeClubItems"
}
}
])
// const availableClubItems = [];
// clubItems.forEach(async clubItem => {
// const booking = Booking.findOne({ 'clubItems._id': clubItem._id}).where({date: bookingDateTime})
// if(!booking)
// {
// availableClubItems.push(clubItem);
// }
// })
console.log(clubItems);
res.status(200).send(clubItems);
})
module.exports = router;
my commented approach is implementing the logic with foreach loop and pushing a club item into an empty array if the booking with the given date, time and clubItemID is not found.
my 2nd approach is using aggregation using $lookup and $match but i reached no where.
router.get('/getPlaystationRooms/:datetime', async (req, res) => {
const bookingDateTime = req.params.datetime
const clubItems = await ClubItem.aggregate([
{
$lookup: {
from: "booking",
localField: "_id",
foreignField: "clubItems._id",
as: "freeClubItems"
}
}
])
// const availableClubItems = [];
// clubItems.forEach(async clubItem => {
// const booking = Booking.findOne({ 'clubItems._id': clubItem._id}).where({date: bookingDateTime})
// if(!booking)
// {
// availableClubItems.push(clubItem);
// }
// })
console.log(clubItems);
res.status(200).send(clubItems);
})
Im relatively new to MongoDB and Mongoose. Im much used to MySQL so in used to inner joining tables on calls. Ive read a lot that you can link two Mongoose Schemas to achieve the same outcome. How would like like the two schemas together to when I make a call to get a chore by id it'll return the chore and then for the assignedTo & createdBy have the user scheme data for the said userId?
Chore Schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ChoreSchema = new Schema({
title: {
type: String,
required: true
},
desc: {
type: String,
required: true
},
time: {
type: Number,
required: true
},
reaccurance: {
type: [{
type: String,
enum: ['Daily', 'Weekly', 'Bi-Weekly', 'Monthly']
}]
},
reward: {
type: Number,
required: true
},
retryDeduction: {
type: Number,
required: false
},
createdDate: {
type: Date,
default: Date.now
},
createdBy: {
type: String,
required: true
},
dueDate: {
type: Date,
required: true
},
status: {
type: [{
type: String,
enum: ['new', 'pending', 'rejected', 'completed', 'pastDue']
}],
default: ['new']
},
retryCount: {
type: Number,
default: 0,
required: false
},
rejectedReason: {
type: String,
required: false
},
familyId: {
type: String,
required: true
},
assignedTo: {
type: String,
required: false,
default: ""
}
});
let Chores = module.exports = mongoose.model('Chores', ChoreSchema);
module.exports.get = function (callback, limit) {
Chores.find(callback).limit(limit);
};
User Schema
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
role: {
type: [{
type: String,
enum: ['Adult', 'Child']
}]
},
birthday: {
type: String,
required: false
},
familyId: {
type: String,
required: true
},
balance: {
type: Number,
required: true,
default: 0.00
}
});
let Users = module.exports = mongoose.model('Users', UserSchema);
module.exports.get = function (callback, limit) {
Users.find(callback).limit(limit);
};
Im trying to link ChoreSchema.createdBy & ChoreScheme.assignedTo by UserSchema._id
How I make the call in Node.js:
exports.index = function(req, res) {
Chore.get(function(err, chore) {
if (err)
res.send(err);
res.json({
message: 'Chore List',
data: chore
});
});
};
Mongoose has a more powerful alternative called populate(),
which lets you reference documents in other collections.
https://mongoosejs.com/docs/populate.html
Here is how you can link ChoreSchema.createdBy and ChoreScheme.assignedTo by UserSchema._id
var mongoose = require('mongoose');
const { Schema, Types } = mongoose;
var UserSchema = new Schema({
firstName: { type: String, required: true },
...
})
var ChoreSchema = new Schema({
title: { type: String, required: true },
...
//The ref option is what tells Mongoose which model to use during population
assignedTo: { type: Types.ObjectId, ref: 'Users' },
createdBy: { type: Types.ObjectId, ref: 'Users' },
})
let Chores = mongoose.model('Chores', ChoreSchema);
let Users = mongoose.model('Users', UserSchema);
Then in your express route handler you can populate assignedTo & createdBy like this
router.get('/chores/:id', function (req, res) {
const choreId = req.params.id;
Chores.find({ _id: choreId })
.populate('createdBy') // populate createdBy
.populate('assignedTo') // populate assignedTo
.exec(function (err, chore) {
if(err) {
return res.send(err)
}
res.json({ message: 'Chore List', data: chore });
});
})
In my NodeJS API and MongoDB, I'm trying to delete a record which is a reference to another collection.
What I would like to do is to delete the referred objectId and the records related to the other collection which is referred.
I have 2 models Profiles and Posts and I want to delete the same one post from Profile and Post collection.
I was able to delete the reference id in Profile but I don't know how to delete also the record from Posts collection.
I tried this:
async delete(req, res) {
try {
// Match with username and pull to remove
await Profile.findOneAndUpdate(
{ _id: res.id._id },
{ $pull: { posts: req.params.postId } },
err => {
if (err) {
throw new ErrorHandlers.ErrorHandler(500, err);
}
res.json({ Message: "Deleted" });
}
);
} catch (error) {
res.status(500).send(error);
}
}
And my 2 models:
// Here defining profile model
// Embedded we have the Experience as []
const { Connect } = require("../db");
const { isEmail } = require("validator");
const postSchema = {
type: Connect.Schema.Types.ObjectId,
ref: "Post"
};
const experienceSchema = {
role: {
type: String,
required: true
},
company: {
type: String,
required: true
},
startDate: {
type: Date,
required: true
},
endDate: {
type: Date,
required: false
},
description: {
type: String,
required: false
},
area: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
},
username: {
type: String,
required: false
},
image: {
type: String,
required: false,
default: "https://via.placeholder.com/150"
}
};
const profileSchema = {
firstname: {
type: String,
required: true
},
surname: {
type: String,
required: true
},
email: {
type: String,
trim: true,
lowercase: true,
unique: true,
required: [true, "Email is required"],
validate: {
validator: string => isEmail(string),
message: "Provided email is invalid"
}
},
bio: {
type: String,
required: true
},
title: {
type: String,
required: true
},
area: {
type: String,
required: true
},
imageUrl: {
type: String,
required: false,
default: "https://via.placeholder.com/150"
},
username: {
type: String,
required: true,
unique: true
},
experience: [experienceSchema],
posts: [postSchema],
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
}
};
const collectionName = "profile";
const profileSchemaModel = Connect.Schema(profileSchema);
const Profile = Connect.model(collectionName, profileSchemaModel);
module.exports = Profile;
const { Connect } = require("../db");
const reactionSchema = {
likedBy: {
type: String,
unique: true,
sparse: true
}
};
const postSchema = {
text: {
type: String,
required: true,
unique: true,
sparse: false
},
profile: {
type: Connect.Schema.Types.ObjectId,
ref: "Profile",
},
image: {
type: String,
default: "https://via.placeholder.com/150",
required: false
},
createdAt: {
type: Date,
default: Date.now,
required: false
},
updatedAt: {
type: Date,
default: Date.now,
required: false
},
reactions: [reactionSchema],
comments: {
type: Connect.Schema.Types.ObjectId,
ref: "Comment",
required: false
}
};
const collectionName = "post";
const postSchemaModel = Connect.Schema(postSchema);
const Post = Connect.model(collectionName, postSchemaModel);
module.exports = Post;
Just add a query to remove the post after pulling it's ID from the profile collection:
async delete(req, res) {
try {
// Match with username and pull to remove
await Profile.findOneAndUpdate(
{ _id: res.id._id },
{ $pull: { posts: req.params.postId } },
// You don't need an error callback here since you are
// using async/await. Handle the error in the catch block.
);
await Posts.remove({ _id: req.params.postId });
} catch (error) {
// This is where you handle the error
res.status(500).send(error);
}
}
I have a item model where it a virtual field to refer stock badges.
'use strict';
const mongoose = require('mongoose');
const mongooseHidden = require('mongoose-hidden')();
const Badge = mongoose.model('Badge');
const validateProperty = function(property) {
return (property.length);
};
const Schema = mongoose.Schema;
const ItemSchema = new Schema({
itemCode: {
type: Number,
index: {
unique: true,
sparse: true // For this to work on a previously indexed field, the index must be dropped & the application restarted.
},
required: true
},
itemName: {
type: String,
uppercase: true,
trim: true
},
barcode: {
type: String,
trim: true
},
category: {
type: Schema.Types.ObjectId,
ref: 'Category'
},
subCategory: {
type: Schema.Types.ObjectId,
ref: 'SubCategory'
},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
enum: [
'active', 'inactive', 'removed'
],
default: 'active'
}
}, {id: false});
ItemSchema.virtual('badges').get(function() {
return this.getAvailableBadges();
});
ItemSchema.methods.getAvailableBadges = function() {
Badge.find({
item: this._id
}, (err, badges) => {
if (badges) {
return badges;
} else {
return [];
}
});
};
ItemSchema.set('toJSON', {virtuals: true});
ItemSchema.set('toObject', {virtuals: true});
ItemSchema.plugin(mongooseHidden, {
hidden: {
_id: false,
__v: true
}
});
mongoose.model('Item', ItemSchema);
And batch model as below
'use strict';
const mongoose = require('mongoose');
const mongooseHidden = require('mongoose-hidden')();
const validateProperty = function(property) {
return (property.length);
};
const Schema = mongoose.Schema;
const BadgeSchema = new Schema({
item: {
type: Schema.Types.ObjectId,
ref: 'Item'
},
qty: {
type: Number,
validate: [validateProperty, 'Please enter Quantity !']
},
purchasingPrice: {
type: Number,
validate: [validateProperty, 'Please enter purchasingPrice !']
},
sellingPrice: {
type: Number,
validate: [validateProperty, 'Please enter sellingPrice !']
},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
status: {
type: String,
enum: [
'active', 'inactive', 'removed'
],
default: 'active'
}
});
BadgeSchema.plugin(mongooseHidden, {
hidden: {
_id: false,
__v: true
}
});
mongoose.model('Badge', BadgeSchema);
Item's badge virtual field doesn't got populated.
How are we going to work with async getter method
I have put some console log statements and found that getAvailableBadges is getting data.
I need to send json object with virtual field values via express. How to I do it?
What I did was create an virtual property
ItemSchema.virtual('badges', {
ref: 'Badge',
localField: '_id',
foreignField: 'item'
});
And populate it with
{
path: 'badges',
select: [
'qty', 'purchasingPrice', 'sellingPrice'
],
options: {
sort: {
'created': -1
}
}
}
Well, the operations are asynchronous so you have to wait for the callback to fire.
You can only return the values by passing it in the callback (or you can set the values of the current object prior to calling the callback).
I think it would be something like this:
ItemSchema.virtual('badges').get(function (callback) {
Badge.find({ item: this._id }, callback);
};
Then you would use it like
item.badges(function (err, badges) {
// do something with badges
});
I'm attempting to use a model ("goal") in my "like" model class (Code below). However every time I startup the node instance it says that Goal.findById is not a function.
After running a console.log(number) in each model, I found that they load very oddly and out of the order I would like/need.
I was wondering how I can load models before others or set a specific load order for models?
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = require('../models/user.js');
var Goal = require('../models/goal.js');
var likeSchema = new Schema({
userPosted: {
type: Number,
ref: 'user',
required: true
},
goal: {
type: Number,
ref: 'goal',
required: true
}},
{
timestamps: true
});
likeSchema.post('save', function (doc, next) {
var goalID = doc.goal;
Goal.findOne({'_id': doc.goal}, function(err, goal) {
goal.likes.push(doc._id);
goal.save();
User.findById(doc.userPosted, function(err, user) {
user.likedPosts.push(goalID);
user.save();
next();
});
});
});
likeSchema.post('remove', function(doc) {
Goal.findById(doc.goal, function(err, goal) {
goal.likes.pull(doc._id);
goal.save();
User.findById(doc.userPosted, function(err, user) {
user.likedPosts.pull(goal._id);
user.save();
});
});
});
console.log("4");
module.exports = mongoose.model('like', likeSchema);
Error:
TypeError: Goal.findOne is not a function at model.<anonymous> (E:\Project\like.js:28:10)
**Edit: ** goal.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var autoIncrement = require('mongoose-auto-increment');
var User = require('../models/user.js');
var Like = require('../models/like.js');
var goal = new Schema({
user: {
type: Number,
ref: 'user',
required: true
},
user_name: {
type: String,
required: true
},
title: {
type: String,
required: true,
trim: true
},
description: {
type: String,
default: undefined,
trim: true
},
location: {
type: String,
default: undefined
},
likes: [{
type: Schema.ObjectId,
ref: 'like'
}],
comments: [{
type: Schema.ObjectId,
ref: 'comment'
}],
updates: [{
type: Schema.ObjectId,
ref: 'update'
}],
created: {
type: Date,
default: Date.now
},
cover_image: {
type: String,
default: undefined
},
complete_by: {
type: String,
default: "Death"
},
completed: {
type: Boolean,
default: false
},
completedDate: {
type: String,
default: undefined
},
url: {
type: String,
default: undefined
},
sponsor: {
type: String,
default: undefined
},
private: {
type: Boolean,
default: false
}
});
goal.plugin(autoIncrement.plugin, 'goal');
goal.pre('save', function (next) {
this.wasNew = this.isNew;
next();
});
goal.post('save', function (doc) {
if (this.wasNew) {
User.findById(doc.user, function (err, user) {
user.goals.push(doc._id);
user.save();
});
}
});
goal.post('remove', function(doc) {
//TODO: Removes like objects/user profile/anything containing the goal
User.findById(doc.user, function(err, user) {
user.goals.pull(doc._id);
user.save();
});
Like.find({
'goal': doc._id
}).remove(function(err, removed) {
if(err) {
console.log("ERROR?");
return;
}
console.log('removed likes - ' + removed);
});
});
module.exports = mongoose.model('goal', goal);
I think your likeschema would be something like this -
var likeSchema = new Schema({
userPosted: {
type: Number,
ref: 'User',
required: true
},
goal: {
type: Number,
ref: 'Goal',
required: true
}},
{
timestamps: true
});
If it doesn't work, can you please show your code for Goal model?