Cannot pull item from a document's property's array - javascript

I have 2 models: Commitments and Users. The Users model has a property named commitments whose value is an array of ObjectIds and reference is the Commitments collection:
const modelName = 'users';
const schema = new mongoose.Schema<UserProperties, UsersModel, UserVirtuals>({
(...)
commitments: {
type: [
{
type: mongoose.SchemaTypes.ObjectId,
ref: 'commitments',
},
],
required: true,
default: [],
},
}, {
collection: modelName,
}).loadClass(User);
The Commitments model has a property named owner whose value is an ObjectId that references a document from the Users model's collection:
const modelName = 'commitments';
const schema = new mongoose.Schema<CommitmentProperties, CommitmentsModel, CommitmentVirtuals>({
(...)
owner: {
type: mongoose.SchemaTypes.ObjectId,
required: true,
},
}, {
collection: modelName,
}).loadClass(Commitment);
I wanted to make it so whenever I deleted a document from the Commitments model's collection, it would also remove the ObjectId of that document from the Users model document of ObjectId owner's commitments property. So, in my Commitments model's schema:
schema.pre('deleteOne', async function() {
(<UsersModel>storage.models.get('users')).findOneAndUpdate({
_id: this.owner,
}, {
$pull: {
commitments: this._id,
},
}).exec().catch(reason => {
logger.log({
category: LoggingCategories.MIDDLEWARE,
type: LoggingType.ERROR,
message: reason,
extraInfo: 'COULD NOT REMOVE COMMITMENT FROM ARRAY OF USER COMMITMENTS',
});
});
});
However this is not working. I don't get any errors and I'm sure that the middleware function is getting executed because I tried logging something within it to test and it did log.

Related

mongoose populate doesn't work with array of IDs

I have these two 3 models User, Product and Orders and are also has references to each other.
My Orders Schema:
const orderSchema = Schema({
buyerId:{
type: Schema.Types.ObjectId,
ref: 'User'
},
totalAmount: {
type: Number,
required: [true, "description is required"]
},
createdOn: {
type: Date,
default: new Date()
},
items:[{type:Schema.Types.ObjectId, ref: 'Product'}]})
I'm trying to use populate() like this:
Order.find()
.populate('buyerId')//Reference to User Model
.populate('items')// Reference to Product Model
.exec(function (err, result){
console.log(result);// RETURNS ONLY buyerId populated
console.log(result.buyerId.name);//Successfully references to my User model and prints name
console.log(result.items);//Prints Undefined
})
You can see my console log above and what it returns is only the populated buyerId(only got the reference from my User model)
Seems like my populate('items') doesnt work at all. The items field contains array of IDs, where the IDs are those of products. I need to reference to User and Product both. I'm just following the documentation in mongoose, I don't know what I might be doing wrong.
use aggregate
Order.aggregate([
{ $match:{ user:"sample_id"
}
},
{$lookup:{
from:'users', // users collection name
localField:'buyerId',
foreignField:'_id',
as:'buyerId'
}},
{
$lookup:{
from:'items', //items collection name
localField:'items',
foreignField:'_id',
as:'items'
}
},
])

Mongoose One to Many relatioship

I'm trying to create a one to many relationship between Category and Services models.
I have two schemas: Category and Service.
Category schema holds services array.
Service schema hold category _id.
I'm trying to retrieve all Categories including services.
Category schema:
import mongoose from 'mongoose';
const categoriesModel = new mongoose.Schema(
{
category: {
type: String,
required: true,
},
services: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'Service' }],
},
{ timestamps: true }
);
export const Category = mongoose.model('Category', categoriesModel);
Service schema:
import mongoose from 'mongoose';
const serviceSchema = new mongoose.Schema(
{
serviceName: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
images: {
type: Array,
image: Buffer,
required: true,
},
category: {
type: mongoose.SchemaTypes.ObjectId,
ref: 'Category',
required: true,
},
},
{ timestamps: true }
);
export const Service = mongoose.model('Service', serviceSchema);
HTTP get request to retrieve all Categories including Services:
export const getAllCategories = async (req, res) => {
try {
const docs = await Category.find({}).populate({
path: 'services',
model: 'Service',
});
console.log(docs);
res.status(200).json({ data: docs });
} catch (err) {
res.status(400).json(err.message);
}
};
This is what I get as a response from the request above:
{
"services": [],
"_id": "60affe06c71901281d3d820d",
"category": "Electricity"
},
I've tried different ways to populate services but none of them worked.
What I've tried so far:
Add additional nesting to services attribute.
Adding object to the populate params:
{
path: 'services',
model: 'Service',
}
Removing existing data ant posting additional one.
Fun fact. Retrieving each Service individually, service data includes the actual category, that it's been assigned to:
export const getAllServices = async (req, res) => {
try {
const doc = await Service.find({}).populate('category');
res.status(200).json({ data: doc });
} catch (err) {
res.status(400).json(err);
}
};
Is there something I'm missing regarding including services within retrieving categories? It's really interesting, because somehow the actual services data is [].
EDIT: Each category in Categories collection has empty services array. The services are added dynamically, they might be null for specific category until user adds a service and assigns a category to it. Is this forbidden? Do I need to find specific category and add a specific service to category while creating new service? If yes, this would require to add additional Category model to the services.controller.js and find the category. Is this really an appropriate solution?

Mongoose .stream doesn't have populated fields

I have a schema "Reports" that looks like this:
var Reports = new Schema(
{
identifiersub: { // Id of reported submission, populate stuff
type: Schema.Types.ObjectId,
ref: "Submission"
},
identifiercom: { // Id of reported comment, populate stuff
type: Schema.Types.ObjectId,
ref: "Comments"
},
identifieruse: { // Id of reported user, populate stuff
type: Schema.Types.ObjectId,
ref: "AccountDetails"
},
solved: { // Whether this problem has been solved or not
type: Boolean,
default: false
},
processed: [{ // Array of moderators who were participating in processing this report
type: Schema.Types.ObjectId,
ref: "AccountDetails"
}],
type: String, // Type of content. bug, user, submission or comment
reports: [ // Additional text for each type of reported content
{
description: String, // Text from select component. For bugs: the feature that is affected,
reason: String, // Reason for this report
by: { // Reporter
type: Schema.Types.ObjectId,
ref: "AccountDetails"
}
}
],
notes: [{ // Admin notes
title: String, // Fleshed out discussion/reasoning
note: String, // Decided outcome that each note represents
outcome: String, // ("Keep reported", "delete", etc)
date: Date, // Date this note was added
moderator: { // Moderator who added this note
type: Schema.Types.ObjectId,
ref: "AccountDetails"
}
}]
},
{ strict: false, timestamps: true }
)
And in my admin panel I want to implement a search function that searches all reports with the specified keyword. The most important field here is reports: it's an array of objects containing a "by" field which is an ObjectId. Now I wanted to populate the username for this id, but I'm not seeing it in my document...I use the .stream method to check the whole document including nested objects and arrays of objects. Here's my query:
var cursor = Reports
.find({ type: req.query.type})
.limit(500)
.populate("notes.moderator reports.by processed", "username")
.populate("identifieruse", "username dob email ipaddress")
.populate("identifiercom", "by.username comment")
.populate("identifiersub", "meta.title by deleted")
.sort("-createdAt")
.lean()
.stream();
cursor.on('data', function(doc) {
console.log(doc);
if (doc.toString().includes(key)) results.push(doc)
})
cursor.on('error', function(err) {
return catcherror(new Error(err), res)
})
cursor.on('close', function() {
console.log(results);
return res.send(results)
})
Thanks for help!
Use .cursor() instead of .stream() and it works.

Cast to ObjectId failed for value error in Mongoose findOne

I've been struggling with a weird exception and still confused about it after an hour.
CastError: Cast to ObjectId failed for value "pedrammarandi#gmail.com"
at path "_id" for model "Account"
I'm trying to retrieve an Account via email address. Here is my query
export async function getPendingRecipients(user_id, email_address) {
const account = await Account
.find({email: email_address})
.exec();
return true;
}
This is my Schema object
const userGmailSchema = new Schema({
id: {
type: String,
unique: true
},
displayName: String,
image: Object,
accessToken: String,
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
refreshToken: {
type: String,
default: null
},
email: {
type: String,
unique: true
},
emails: [
{
type: Schema.Types.ObjectId,
ref: 'Emails'
}
]
});
I'm not sure, but I guess the problem is you wrote an id field.
In MongoDB, the "primary key" is _id field, which is an ObjectId object (actually it's a 12-byte-value), and in mongoose, id is a virtual getter of _id, easily said, id is an alias of _id.
(A little different is that, _id returns ObjectId, id returns String version of _id.)
By default, mongoose manage _id field automatically, so commonly we should not write anything about id in schema.
If your id is for something like primary key ID in SQL DB, just remove it from mongoose schema. If it's means something else in your app, try to add an option:
const userGmailSchema = new Schema({
// your schemas here
},
{
{ id: false } // disable the virtual getter
})
or rename it.
http://mongoosejs.com/docs/guide.html#id
Hope this helps.

Understanding upsert with $push

I have a region collection:
var RegionSchema = new Mongoose.Schema({
name: {type: String},
registrations: [{type: Mongoose.Schema.Types.ObjectId, ref: 'registrations'}],
...
});
A registration collection:
var RegistrationSchema = Mongoose.Schema({
firstName: {type: String},
...
});
In my controller I instantiate a registration then save it to my region with the upsert option set to true:
var registration = new Registration(req.body.registration);
...
Region
.update(
{ _id: user.region},
{ $push: {registrations: registration},
{ upsert: true }
)
.exec();
What I find is that an ObjectId("...") does, in fact, get pushed onto the registrations property of the region, e.g.:
{
name: "Northwest",
registrations: [ObjectId("57d038a1466345d52920b194")]
}
but there is no matching document with that _id in the registrations collection. So my question: what am I not understanding about the nature of the upsert flag; does it not suggest that calling save on the registration is unnecessary?
The upsert flag only applies to the collection you're operating on; in this case, the Region collection. So, when calling Region.update, it will create an object with _id of user.region if an object with that ID doesn't exist.
Mongo doesn't enforce strict ID references, so it will let you push an ID onto registrations so long as the ID is valid.
You'll need to save the Registration object first:
var registration = new Registration(req.body.registration);
registration.save(function() {
Region.update(
{ _id: user.region },
{ $push: { registrations: registration } },
{ upsert: true }
).exec();
});

Categories

Resources