Use 'virtual' in an array of objects (Mongoose) - javascript

I have two schemas as the following:
const castListSchema = new Schema({
id: String,
roleInVideo: String
});
const videoSchema = new Schame({
castList: { type: [castListSchema], default: [] }
});
I want to get data by the following structure:
castList: [
{
details: { ... },
roleInVideo: 'director'
},
...
]
I want to use virtual not aggregate and etc...
Is it possible? How?

Yes, you can use the virtual function inside the array of objects.
Use the following code to use the virtual function for the array of objects.
videoSchema.virtual("castList.castData", {
ref: "new_assets",
localField: "castList.castListSchema",
foreignField: "_id",
justOne: true
});
In populate also use this "castList.castData" as key to retrieve the data.

Related

problem with mongoose virtual populate (populated field is missing)

i was using mongoose in my project with typescript when i add virtual called subdomains to populate the data from another collection its working without error but i can't print the populated data directly, i can only access the value using console.log((r as any).subdomains) but when print the console.log(r as any) there is no field called subdomains,please check example 1 and example 2
export interface IDomain{
domain:string
owner:Schema.Types.ObjectId,
}
const domainSchema = new Schema<IDomain>({
domain:{type:String,required:true},
owner:{type:Schema.Types.ObjectId,required:true},
})
domainSchema.virtual("subdomains",{
ref:"DomainMonitor",
localField: '_id',
foreignField: 'owner'})
Querying the database | example 1
Domain.findOne({owner:"633ebe32d0733c0a8eb4f8d6"}).populate("subdomains").then(r=>{
console.log(domains)
})
output | example 1
{
_id: new ObjectId("6342fa2d4b730f004704eb45"),
domain: 'dummy.com',
__v: 0,
owner: new ObjectId("633ebe32d0733c0a8eb4f8d6")
}
Query the database | example 2
Domain.findOne({owner:"633ebe32d0733c0a8eb4f8d6"}).populate("subdomains").then(r=>{
const domains= (r as any).subdomains
console.log(domains)
})
output | example 2
[{
_id: new ObjectId("6342fa314b730f004704eb47"),
owner: new ObjectId("6342fa2d4b730f004704eb45"),
subdomain: [
'dummy',
],
wildcard: [ '*.dummy.com', '*.dummy.com' ],
__v: 1
}]
Another approach where you represent your schema as object, not as JSON (using .-operator) is the following:
// create a new schema
const MessageSchema = new mongoose.Schema(
{
channel: { type: String },
content: { type: String, required: true },
moderator: { type: Number },
},
// Add this to your Schema
{
toObject: { virtuals: true },
// alternatively: toJSON: { virtuals: true}, if you want your output as json
}
);
// Approach 2:
const mySchema = new mongoose.Schema({
domain: { type:String, required:true },
owner: { type:Schema.Types.ObjectId, required:true }
});
mySchema.set('toObject', { virtuals: true })
Mongoose Documentation says:
Keep in mind that virtuals are not included in toJSON() and toObject() output by default. If you want populate virtuals to show up when using functions like Express' res.json() function or console.log(), set the virtuals: true option on your schema's toJSON and toObject() options.
The reason is stated in the mongoose documentation (https://mongoosejs.com/docs/guide.html):
If you use toJSON() or toObject() mongoose will not include virtuals by default. This includes the output of calling JSON.stringify() on a Mongoose document, because JSON.stringify() calls toJSON(). Pass { virtuals: true } to either toObject() or toJSON().
Basically your console.log statement makes a call to toJSON and does not include virtuals by default.
You can include the following in your schema definition in order to include virtuals in your toJSON calls:
domainSchema.set('toJSON', { virtuals: true });

Query builder how to use "where" clause on populated documents

I'm having some issues using the Mongoose queries. I'm just trying to find documents in a collection using the where clause.
Unfortunately, it seems you can't use the where clause on populated documents.
This is the collection schema
const schema: Schema = new Schema({
game: { type: Schema.Types.ObjectId, ref: 'Game', required: true, index: true },
players: [{ type: Schema.Types.ObjectId, ref: 'Player', required: true, index: true }],
scores: [{ type: String }],
resultDate: { type: Date}
});
I'm trying to find all games where type = 1v1, so I tried this
let query = Matchs.find()
.populate('game')
.populate('players')
.where('game.name').equals('Trackmania')
const matchs: Match[] = await query.exec();
This returns an empty array.
Notes
Removing the where clause returns the correct results (all Matchs)
Any where clause on game returns an empty array
I'd like to use query builder instead of passing a json because I use some parameters to define what I am querying
I read that where clause didn't work on nested documents, but there must be a way to do this right ? Am I missing something ?
You can Use Lookup and Match instead of Where.
MatchesModel.aggregate([
const GameName = req.query;
{"$lookup":{
"from":"games", // name of the foreign collection
"localField":"game",
"foreignField":"_id",
"as":"game"
}},
{"$lookup":{
"from":"players",
"localField":"players",
"foreignField":"_id",
"as":"players"
}},
{"$match":{
"game.name":{
"$eq": GameName
}
}}
])

How to filter only some rows of an array inside a document?

I have a MongoDB collection of documents, using a schema like this:
var schema = new Schema({
name: String,
images: [{
uri: string,
active: Boolean
}]
});
I'd like to get all documents (or filter using some criteria), but retrieve - in the images array - only the items with a specific property (in my case, it's {active: true}).
This is what I do now:
db.people.find( { 'images.active': true } )
But this retrieves only documents with at least one image which is active, which is not what I need.
I know of course I can filter in code after the find is returned, but I do not like wasting memory.
Is there a way I can filter array items in a document using mongoose?
Here is the aggregation you're looking for:
db.collection.aggregate([
{
$match: {}
},
{
$project: {
name: true,
images: {
$filter: {
input: "$images",
as: "images",
cond: {
$eq: [
"$$images.active",
true
]
}
}
}
}
}
])
https://mongoplayground.net/p/t_VxjfiBBMK

'InvalidPipelineOperator' with $lookup when adding .toArray()

I'm new to MongoDB, and working with aggregation. Here goes:
I have two collections 'video_card' and 'vendors'. I am fetching a video card document from the collection with the following structure:
_id:ObjectId(x),
vendorList:
0: ID1;
1: ID2;
I am trying to do a join between this document and this vendor collection:
_id: id:ObjectId(y)
name: "Amazon"
My aggregate is as follows so far:
const products = await db
.collection("video_card")
.aggregate([
{
$project: {
mappedVendors: {
$map: {
input: "$vendorList",
as: "vendorName",
in: {
$lookup: {
from: "vendors",
localField: "vendorList",
foreignField: "name",
as: "VendorNames"
},
},
},
},
},
},
]);
This returns a cursor object. However, when I attach .toArray() to the end of this, I get a code:168 'InvalidPipelineOperator'. Why is this?
To clarify, my intent is to return the data with vendorList ids swapped with names.
To follow up, I realized that my foreignField in the $lookup was incorrect, and changed it to _id. I hope this helps other people learning aggregate functions.

Mongoose: Adding Attributes to Nested Objects

I'm currently using AngularJS, and the backend is using NodeJS and Express. I use Mongoose to access the database. I'm trying to figure how to add attributes to nested objects and I can't for the life of me find out how to do it anywhere on the web.
My Schema looks like this:
{
id: {
type: String
},
involved: {
type: String
},
lastMsgRead: Object
}
lastMsgRead will look something like this:
{
user1: "somestringblahblah",
user2: "someotherstring",
}
and so on.
My question is, how would I update lastMsgRead with Mongoose to add another attribute to it, such as adding user3 so it now looks like:
{
user1: "somestringblahblah",
user2: "someotherstring",
user3: "anotherstring"
}
The entire document would like this after the update:
{
id: "string",
involved: "string",
lastMsgRead: {
user1: "somestringblahblah",
user2: "someotherstring",
user3: "anotherstring"
}
}
Edit:
After I add the attribute, how would I then update it in the future?
You can use .dot notation to update in nested object
db.collection.update(
{ },
{ "$set": { "lastMsgRead.user3": "anotherstring" } }
)
in mongoose 5.1.0 and up you can approach this with the MongooseMap.
You can define it in the schema as followed:
{
id: {
type: String
},
involved: {
type: String
},
lastMsgRead: {
type: Map,
of: String
}
}
you can then add a new value by .set(key, value)
myDoc.lastMsgRead.set(key, value)
and get a value by .get(key)
myDoc.lastMsgRead.get(key)

Categories